From 432cf47f4d4e2d410a7b9d94349285eb1457e674 Mon Sep 17 00:00:00 2001 From: Makar Ivashko <1212makar1212@gmail.com> Date: Fri, 27 Mar 2026 11:17:48 -0700 Subject: [PATCH 01/91] Add Vec3d math functions: dot, cross, normalize, mag, distSq, latLngToVec3 From uber/h3#1052. --- src/h3lib/include/vec3d.h | 19 +++++++++++++++---- src/h3lib/lib/vec3d.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index 0bd9ac5a36..a43d9aa31c 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -20,18 +20,29 @@ #ifndef VEC3D_H #define VEC3D_H +#include "h3api.h" #include "latLng.h" -/** @struct Vec3D +/** @struct Vec3d * @brief 3D floating point structure + * + * For geodesic calulations represents a point on the surface of the Earth + * as a unit vector in 3D Cartesian space (ECEF-like coordinates). */ typedef struct { - double x; ///< x component - double y; ///< y component - double z; ///< z component + double x; ///< x component (towards 0° lat, 0° lon) + double y; ///< y component (towards 0° lat, 90° lon) + double z; ///< z component (towards north pole) } Vec3d; void _geoToVec3d(const LatLng *geo, Vec3d *point); double _pointSquareDist(const Vec3d *p1, const Vec3d *p2); +double vec3Dot(const Vec3d *v1, const Vec3d *v2); +void vec3Cross(const Vec3d *v1, const Vec3d *v2, Vec3d *out); +void vec3Normalize(Vec3d *v); +double vec3MagSq(const Vec3d *v); +double vec3Mag(const Vec3d *v); +double vec3DistSq(const Vec3d *v1, const Vec3d *v2); +void latLngToVec3(const LatLng *geo, Vec3d *v); #endif diff --git a/src/h3lib/lib/vec3d.c b/src/h3lib/lib/vec3d.c index 0b95f1327c..8db1b3b372 100644 --- a/src/h3lib/lib/vec3d.c +++ b/src/h3lib/lib/vec3d.c @@ -21,6 +21,8 @@ #include +#include "constants.h" + /** * Square of a number * @@ -54,3 +56,38 @@ void _geoToVec3d(const LatLng *geo, Vec3d *v) { v->x = cos(geo->lng) * r; v->y = sin(geo->lng) * r; } + +double vec3Dot(const Vec3d *v1, const Vec3d *v2) { + return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; +} + +void vec3Cross(const Vec3d *v1, const Vec3d *v2, Vec3d *out) { + out->x = v1->y * v2->z - v1->z * v2->y; + out->y = v1->z * v2->x - v1->x * v2->z; + out->z = v1->x * v2->y - v1->y * v2->x; +} + +void vec3Normalize(Vec3d *v) { + double mag = vec3Mag(v); + // Check for zero-length vector to avoid division by zero. + // Using a small epsilon for robustness. + if (mag > EPSILON) { + double invMag = 1.0 / mag; + v->x *= invMag; + v->y *= invMag; + v->z *= invMag; + } +} + +double vec3MagSq(const Vec3d *v) { return vec3Dot(v, v); } + +double vec3Mag(const Vec3d *v) { return sqrt(vec3Dot(v, v)); } + +double vec3DistSq(const Vec3d *v1, const Vec3d *v2) { + double dx = v1->x - v2->x; + double dy = v1->y - v2->y; + double dz = v1->z - v2->z; + return dx * dx + dy * dy + dz * dz; +} + +void latLngToVec3(const LatLng *geo, Vec3d *v) { _geoToVec3d(geo, v); } From 4b6daca67e54458207ace15babd9b41383fc1a4e Mon Sep 17 00:00:00 2001 From: Makar Ivashko <1212makar1212@gmail.com> Date: Fri, 27 Mar 2026 11:30:59 -0700 Subject: [PATCH 02/91] Refactor faceijk.c: Vec3d as core coordinate type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract _vec3dToClosestFace, _vec3dToHex2d, _vec3dToFaceIjk from LatLng wrappers. Add _vec3dAzimuthRads (3D tangent-plane azimuth) and _hex2dToVec3 (inverse via Rodrigues' rotation). Add vec3ToCell and cellToVec3 public API. Existing LatLng API is unchanged — wrappers delegate to the new Vec3d functions. From uber/h3#1052. --- src/h3lib/include/faceijk.h | 3 + src/h3lib/include/h3Index.h | 3 + src/h3lib/lib/faceijk.c | 242 ++++++++++++++++++++++++++++++++---- src/h3lib/lib/h3Index.c | 46 +++++++ 4 files changed, 267 insertions(+), 27 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index c05cbf2988..97f782e788 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -27,6 +27,7 @@ #include "coordijk.h" #include "latLng.h" #include "vec2d.h" +#include "vec3d.h" /** @struct FaceIJK * @brief Face number and ijk coordinates on that face-centered coordinate @@ -73,8 +74,10 @@ typedef enum { // Internal functions void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h); +void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h); void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v); void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g); +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index 6ced3c4cd4..3ef3e9ea48 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -179,4 +179,7 @@ H3Index _h3Rotate60ccw(H3Index h); H3Index _h3Rotate60cw(H3Index h); DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); +H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out); +H3Error cellToVec3(H3Index h3, Vec3d *v); + #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index a408bf0f3c..95be5297b6 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -361,36 +361,56 @@ static const int unitScaleByCIIres[] = { 5764801 // res 16 }; +// Private function declaration +static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd); + /** - * 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. + * Calculates the azimuth from p1 to p2. + * @param p1 The first vector. + * @param p2 The second vector. + * @return The azimuth in radians. */ -void _geoToFaceIjk(const LatLng *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); +static double _vec3dAzimuthRads(const Vec3d *p1, const Vec3d *p2) { + Vec3d northPole = {0.0, 0.0, 1.0}; + + // local north direction on tangent plane. + double NdotC = vec3Dot(&northPole, p1); + Vec3d northDir = {northPole.x - NdotC * p1->x, northPole.y - NdotC * p1->y, + northPole.z - NdotC * p1->z}; + vec3Normalize(&northDir); + + // local east direction on tangent plane + Vec3d eastDir; + vec3Cross(&northDir, p1, &eastDir); + + // vector from p1 to p2 on tangent plane + Vec3d p2_on_tangent; + double p2dotp1 = vec3Dot(p2, p1); + p2_on_tangent.x = p2->x - p2dotp1 * p1->x; + p2_on_tangent.y = p2->y - p2dotp1 * p1->y; + p2_on_tangent.z = p2->z - p2dotp1 * p1->z; + vec3Normalize(&p2_on_tangent); + + // azimuth is angle between north direction and p2_on_tangent + double azimuth = atan2(vec3Dot(&p2_on_tangent, &eastDir), + vec3Dot(&p2_on_tangent, &northDir)); + + return azimuth; } /** * 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 p The Vec3d 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 LatLng *g, int res, int *face, Vec2d *v) { +static void _vec3dToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { // determine the icosahedron face double sqd; - _geoToClosestFace(g, face, &sqd); + _vec3dToClosestFace(p, face, &sqd); // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 double r = acos(1 - sqd * 0.5); @@ -401,9 +421,9 @@ void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v) { } // now have face and r, now find CCW theta from CII i-axis + double p_az = _vec3dAzimuthRads(&faceCenterPoint[*face], p); double theta = - _posAngleRads(faceAxesAzRadsCII[*face][0] - - _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g))); + _posAngleRads(faceAxesAzRadsCII[*face][0] - _posAngleRads(p_az)); // adjust theta for Class III (odd resolutions) if (isResolutionClassIII(res)) @@ -423,6 +443,141 @@ void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v) { v->y = r * sin(theta); } +/** + * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at + * the specified resolution. + * + * @param p The Vec3d 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 _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { + // first convert to hex2d + Vec2d v; + _vec3dToHex2d(p, res, &h->face, &v); + + // then convert to ijk+ + _hex2dToCoordIJK(&v, &h->coord); +} + +/** + * 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 LatLng *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 LatLng *g, int res, int *face, Vec2d *v) { + Vec3d p; + latLngToVec3(g, &p); + _vec3dToHex2d(&p, res, face, v); +} + +/** + * Determines the center point in 3D 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 v3d The 3D coordinates of the cell center point. + */ +void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, + Vec3d *v3d) { + // calculate (r, theta) in hex2d + double r = _v2dMag(v); + + if (r < EPSILON) { + *v3d = faceCenterPoint[face]; + return; + } + + double theta = atan2(v->y, v->x); + + // scale for current resolution length u + for (int i = 0; i < res; i++) r *= M_RSQRT7; + + // scale accordingly if this is a substrate grid + if (substrate) { + r *= M_ONETHIRD; + if (isResolutionClassIII(res)) r *= M_RSQRT7; + } + + 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 && isResolutionClassIII(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 + const Vec3d *center = &faceCenterPoint[face]; + Vec3d northPole = {0.0, 0.0, 1.0}; + + // local north direction on tangent plane. + // N.B. this will not work if the center is at a pole, but + // icosahedron faces are not at the poles. + double NdotC = vec3Dot(&northPole, center); + Vec3d northDir = {northPole.x - NdotC * center->x, + northPole.y - NdotC * center->y, + northPole.z - NdotC * center->z}; + vec3Normalize(&northDir); + + // local east direction on tangent plane + Vec3d eastDir; + vec3Cross(&northDir, center, &eastDir); + + // Rodrigues' rotation formula, simplified for orthogonal vectors + // Direction vector D = northDir * cos(theta) + (center x northDir) * + // sin(theta) where `center x northDir` is `eastDir` + double cosTheta = cos(theta); + double sinTheta = sin(theta); + Vec3d dir = {northDir.x * cosTheta + eastDir.x * sinTheta, + northDir.y * cosTheta + eastDir.y * sinTheta, + northDir.z * cosTheta + eastDir.z * sinTheta}; + + // slerp to get the new point + double cos_r = cos(r); + double sin_r = sin(r); + v3d->x = center->x * cos_r + dir.x * sin_r; + v3d->y = center->y * cos_r + dir.y * sin_r; + v3d->z = center->z * cos_r + dir.z * sin_r; + vec3Normalize(v3d); +} + +/** + * Converts hex 2D coordinates to Vec3d through the LatLng path + * (_hex2dToGeo -> _geoToVec3d), ensuring bitwise-identical results + * with polygon vertices produced by cellToBoundary. + * /** * Determines the center point in spherical coordinates of a cell given by 2D * hex coordinates on a particular icosahedral face. @@ -486,6 +641,20 @@ void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g) { _hex2dToGeo(&v, h->face, res, 0, g); } +/** + * Determines the center point in 3D 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 v3d The 3D coordinates of the cell center point. + */ +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d) { + Vec2d v; + _ijkToHex2d(&h->coord, &v); + _hex2dToVec3(&v, h->face, res, 0, v3d); +} + /** * Generates the cell boundary in spherical coordinates for a pentagonal cell * given by a FaceIJK address at a specified resolution. @@ -597,6 +766,10 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, } } +/** + * Generates the cell boundary in 3D coordinates for a pentagonal cell + * given by a FaceIJK address at a specified resolution. + * /** * Get the vertices of a pentagon cell as substrate FaceIJK addresses * @@ -772,6 +945,10 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, } } +/** + * Generates the cell boundary in 3D coordinates for a cell given by a + * FaceIJK address at a specified resolution. + * /** * Get the vertices of a cell as substrate FaceIJK addresses * @@ -927,27 +1104,38 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { } /** - * Encodes a coordinate on the sphere to the corresponding icosahedral face and - * containing the squared euclidean distance to that face center. + * Encodes a Vec3d coordinate to the corresponding icosahedral face and + * squared euclidean distance to that face center. * - * @param g The spherical coordinates to encode. - * @param face The icosahedral face containing the spherical coordinates. + * @param v3d The Vec3d coordinates to encode. + * @param face The icosahedral face containing the coordinates. * @param sqd The squared euclidean distance to its icosahedral face center. */ -void _geoToClosestFace(const LatLng *g, int *face, double *sqd) { - Vec3d v3d; - _geoToVec3d(g, &v3d); - +static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd) { // determine the icosahedron face *face = 0; // The distance between two farthest points is 2.0, therefore the square of // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); + double sqdT = vec3DistSq(&faceCenterPoint[f], v3d); if (sqdT < *sqd) { *face = f; *sqd = sqdT; } } } + +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing the squared euclidean distance to that face center. + * + * @param g The spherical coordinates to encode. + * @param face The icosahedral face containing the spherical coordinates. + * @param sqd The squared euclidean distance to its icosahedral face center. + */ +void _geoToClosestFace(const LatLng *g, int *face, double *sqd) { + Vec3d v3d; + latLngToVec3(g, &v3d); + _vec3dToClosestFace(&v3d, face, sqd); +} diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 7b48114e50..761cf65810 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1054,6 +1054,35 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { } } +/** + * Encodes a coordinate on the sphere to the H3 index of the containing cell at + * the specified resolution. + * + * The cartesian 3D coordinate is expected to be on the unit sphere. + * + * @param v The 3D cartesian coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param out The encoded H3Index. + * @returns E_SUCCESS on success, another value otherwise + */ +H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out) { + if (res < 0 || res > MAX_H3_RES) { + return E_RES_DOMAIN; + } + if (!isfinite(v->x) || !isfinite(v->y) || !isfinite(v->z)) { + return E_DOMAIN; + } + + FaceIJK fijk; + _vec3dToFaceIjk(v, res, &fijk); + *out = _faceIjkToH3(&fijk, res); + if (ALWAYS(*out)) { + return E_SUCCESS; + } else { + return E_FAILED; + } +} + /** * Convert an H3Index to the FaceIJK address on a specified icosahedral face. * @param h The H3Index. @@ -1159,6 +1188,23 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { return E_SUCCESS; } +/** + * Determines the 3D cartesian coordinates of the center of an H3 cell. + * + * @param h3 The H3 index. + * @param v The 3D cartesian coordinates of the H3 cell center. + * @return E_SUCCESS on success, or another H3Error code on failure. + */ +H3Error cellToVec3(H3Index h3, Vec3d *v) { + FaceIJK fijk; + H3Error e = _h3ToFaceIjk(h3, &fijk); + if (e) { + return e; + } + _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3), v); + return E_SUCCESS; +} + /** * Determines the cell boundary in spherical coordinates for an H3 index. * From fe7b36ac3eb51253b93832ae337c4e0d793a329b Mon Sep 17 00:00:00 2001 From: Makar Ivashko <1212makar1212@gmail.com> Date: Fri, 27 Mar 2026 11:33:40 -0700 Subject: [PATCH 03/91] Add Vec3d unit tests From uber/h3#1052. --- CMakeLists.txt | 1 + CMakeTests.cmake | 1 + src/apps/testapps/testVec3d.c | 84 +++++++++++++++++++++++++++++++++++ src/h3lib/lib/faceijk.c | 13 ------ 4 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 src/apps/testapps/testVec3d.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 42ab9fbef9..43fbc12c7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -255,6 +255,7 @@ set(OTHER_SOURCE_FILES src/apps/testapps/testPolyfillInternal.c src/apps/testapps/testVec2dInternal.c src/apps/testapps/testVec3dInternal.c + src/apps/testapps/testVec3d.c src/apps/testapps/testDirectedEdge.c src/apps/testapps/testDirectedEdgeExhaustive.c src/apps/testapps/testLinkedGeoInternal.c diff --git a/CMakeTests.cmake b/CMakeTests.cmake index aec1190c89..13bfdc6047 100644 --- a/CMakeTests.cmake +++ b/CMakeTests.cmake @@ -242,6 +242,7 @@ add_h3_test(testPolygonInternal src/apps/testapps/testPolygonInternal.c) add_h3_test(testPolyfillInternal src/apps/testapps/testPolyfillInternal.c) add_h3_test(testVec2dInternal src/apps/testapps/testVec2dInternal.c) add_h3_test(testVec3dInternal src/apps/testapps/testVec3dInternal.c) +add_h3_test(testVec3d src/apps/testapps/testVec3d.c) add_h3_test(testCellToLocalIj src/apps/testapps/testCellToLocalIj.c) add_h3_test(testCellToLocalIjInternal src/apps/testapps/testCellToLocalIjInternal.c) diff --git a/src/apps/testapps/testVec3d.c b/src/apps/testapps/testVec3d.c new file mode 100644 index 0000000000..6244aae5f4 --- /dev/null +++ b/src/apps/testapps/testVec3d.c @@ -0,0 +1,84 @@ +/* + * Copyright 2025 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 testVec3d.c + * @brief Tests the vec3d helpers used by the geodesic polyfill path. + */ + +#include + +#include "constants.h" +#include "test.h" +#include "vec3d.h" + +SUITE(Vec3d) { + TEST(dotProduct) { + Vec3d a = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3d b = {.x = -1.0, .y = 0.0, .z = 0.0}; + t_assert(vec3Dot(&a, &b) == -1.0, "dot product matches expected value"); + } + + TEST(crossProductOrthogonality) { + Vec3d i = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3d j = {.x = 0.0, .y = 1.0, .z = 0.0}; + Vec3d k; + vec3Cross(&i, &j, &k); + t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); + t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); + t_assert(fabs(k.z - 1.0) < EPSILON, "z component one"); + t_assert(fabs(vec3Dot(&k, &i)) < EPSILON, "cross is orthogonal to i"); + t_assert(fabs(vec3Dot(&k, &j)) < EPSILON, "cross is orthogonal to j"); + } + + TEST(normalizeAndMagnitude) { + Vec3d v = {.x = 3.0, .y = -4.0, .z = 12.0}; + double magSq = vec3MagSq(&v); + t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); + t_assert(fabs(vec3Mag(&v) - 13.0) < EPSILON, "magnitude matches"); + + vec3Normalize(&v); + t_assert(fabs(vec3Mag(&v) - 1.0) < 1e-12, "normalized vector is unit"); + + Vec3d zero = {.x = 0.0, .y = 0.0, .z = 0.0}; + vec3Normalize(&zero); + t_assert(zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0, + "zero vector remains unchanged when normalizing"); + } + + TEST(distance) { + Vec3d a = {.x = 0.0, .y = 0.0, .z = 0.0}; + Vec3d b = {.x = 1.0, .y = 2.0, .z = 2.0}; + t_assert(fabs(vec3DistSq(&a, &b) - 9.0) < EPSILON, + "distance squared matches"); + } + + TEST(latLngConversionConsistency) { + LatLng geo = {.lat = 0.5, .lng = -1.3}; + Vec3d viaReturn; + latLngToVec3(&geo, &viaReturn); + Vec3d viaOut; + _geoToVec3d(&geo, &viaOut); + + t_assert(fabs(viaReturn.x - viaOut.x) < EPSILON, + "x coordinate consistent"); + t_assert(fabs(viaReturn.y - viaOut.y) < EPSILON, + "y coordinate consistent"); + t_assert(fabs(viaReturn.z - viaOut.z) < EPSILON, + "z coordinate consistent"); + t_assert(fabs(vec3Mag(&viaReturn) - 1.0) < 1e-12, + "converted vector lives on the unit sphere"); + } +} diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 95be5297b6..bafe58a5d1 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -573,11 +573,6 @@ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, vec3Normalize(v3d); } -/** - * Converts hex 2D coordinates to Vec3d through the LatLng path - * (_hex2dToGeo -> _geoToVec3d), ensuring bitwise-identical results - * with polygon vertices produced by cellToBoundary. - * /** * Determines the center point in spherical coordinates of a cell given by 2D * hex coordinates on a particular icosahedral face. @@ -766,10 +761,6 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, } } -/** - * Generates the cell boundary in 3D coordinates for a pentagonal cell - * given by a FaceIJK address at a specified resolution. - * /** * Get the vertices of a pentagon cell as substrate FaceIJK addresses * @@ -945,10 +936,6 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, } } -/** - * Generates the cell boundary in 3D coordinates for a cell given by a - * FaceIJK address at a specified resolution. - * /** * Get the vertices of a cell as substrate FaceIJK addresses * From 4eefbfa284a18f453c2d7a11efb10860dcdf9454 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 22:08:57 -0700 Subject: [PATCH 04/91] Route latLngToCell/cellToLatLng through Vec3d; remove dead LatLng-only code --- src/apps/testapps/testVec3dInternal.c | 27 ++---------- src/h3lib/include/faceijk.h | 4 -- src/h3lib/include/latLng.h | 1 - src/h3lib/include/vec3d.h | 2 +- src/h3lib/lib/faceijk.c | 60 --------------------------- src/h3lib/lib/h3Index.c | 17 +++----- src/h3lib/lib/latLng.c | 13 ------ src/h3lib/lib/vec3d.c | 25 +++-------- 8 files changed, 15 insertions(+), 134 deletions(-) diff --git a/src/apps/testapps/testVec3dInternal.c b/src/apps/testapps/testVec3dInternal.c index 71ffb8a740..b7c4232f40 100644 --- a/src/apps/testapps/testVec3dInternal.c +++ b/src/apps/testapps/testVec3dInternal.c @@ -14,52 +14,31 @@ * limitations under the License. */ -#include #include -#include #include "test.h" #include "vec3d.h" SUITE(Vec3dInternal) { - TEST(_pointSquareDist) { - Vec3d v1 = {0, 0, 0}; - Vec3d v2 = {1, 0, 0}; - Vec3d v3 = {0, 1, 1}; - Vec3d v4 = {1, 1, 1}; - Vec3d v5 = {1, 1, 2}; - - t_assert(fabs(_pointSquareDist(&v1, &v1)) < DBL_EPSILON, - "distance to self is 0"); - t_assert(fabs(_pointSquareDist(&v1, &v2) - 1) < DBL_EPSILON, - "distance to <1,0,0> is 1"); - t_assert(fabs(_pointSquareDist(&v1, &v3) - 2) < DBL_EPSILON, - "distance to <0,1,1> is 2"); - t_assert(fabs(_pointSquareDist(&v1, &v4) - 3) < DBL_EPSILON, - "distance to <1,1,1> is 3"); - t_assert(fabs(_pointSquareDist(&v1, &v5) - 6) < DBL_EPSILON, - "distance to <1,1,2> is 6"); - } - TEST(_geoToVec3d) { Vec3d origin = {0}; LatLng c1 = {0, 0}; Vec3d p1; _geoToVec3d(&c1, &p1); - t_assert(fabs(_pointSquareDist(&origin, &p1) - 1) < EPSILON_RAD, + t_assert(fabs(vec3DistSq(&origin, &p1) - 1) < EPSILON_RAD, "Geo point is on the unit sphere"); LatLng c2 = {M_PI_2, 0}; Vec3d p2; _geoToVec3d(&c2, &p2); - t_assert(fabs(_pointSquareDist(&p1, &p2) - 2) < EPSILON_RAD, + t_assert(fabs(vec3DistSq(&p1, &p2) - 2) < EPSILON_RAD, "Geo point is on another axis"); LatLng c3 = {M_PI, 0}; Vec3d p3; _geoToVec3d(&c3, &p3); - t_assert(fabs(_pointSquareDist(&p1, &p3) - 4) < EPSILON_RAD, + t_assert(fabs(vec3DistSq(&p1, &p3) - 4) < EPSILON_RAD, "Geo point is the other side of the sphere"); } } diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 97f782e788..452b5fc666 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -73,10 +73,7 @@ typedef enum { // Internal functions -void _geoToFaceIjk(const LatLng *g, int res, FaceIJK *h); void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h); -void _geoToHex2d(const LatLng *g, int res, int *face, Vec2d *v); -void _faceIjkToGeo(const FaceIJK *h, int res, LatLng *g); void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); @@ -88,6 +85,5 @@ void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g); Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, int substrate); Overage _adjustPentVertOverage(FaceIJK *fijk, int res); -void _geoToClosestFace(const LatLng *g, int *face, double *sqd); #endif diff --git a/src/h3lib/include/latLng.h b/src/h3lib/include/latLng.h index 8d644e578f..e340756b70 100644 --- a/src/h3lib/include/latLng.h +++ b/src/h3lib/include/latLng.h @@ -52,7 +52,6 @@ bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, double _posAngleRads(double rads); void _setGeoRads(LatLng *p, double latRads, double lngRads); -double _geoAzimuthRads(const LatLng *p1, const LatLng *p2); void _geoAzDistanceRads(const LatLng *p1, double az, double distance, LatLng *p2); diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index a43d9aa31c..cb1da11837 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -36,7 +36,6 @@ typedef struct { } Vec3d; void _geoToVec3d(const LatLng *geo, Vec3d *point); -double _pointSquareDist(const Vec3d *p1, const Vec3d *p2); double vec3Dot(const Vec3d *v1, const Vec3d *v2); void vec3Cross(const Vec3d *v1, const Vec3d *v2, Vec3d *out); void vec3Normalize(Vec3d *v); @@ -44,5 +43,6 @@ double vec3MagSq(const Vec3d *v); double vec3Mag(const Vec3d *v); double vec3DistSq(const Vec3d *v1, const Vec3d *v2); void latLngToVec3(const LatLng *geo, Vec3d *v); +void vec3ToLatLng(const Vec3d *v, LatLng *geo); #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index bafe58a5d1..a1fa50d321 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -460,38 +460,6 @@ void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { _hex2dToCoordIJK(&v, &h->coord); } -/** - * 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 LatLng *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 LatLng *g, int res, int *face, Vec2d *v) { - Vec3d p; - latLngToVec3(g, &p); - _vec3dToHex2d(&p, res, face, v); -} - /** * Determines the center point in 3D coordinates of a cell given by 2D * hex coordinates on a particular icosahedral face. @@ -622,20 +590,6 @@ void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g) { _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, LatLng *g) { - Vec2d v; - _ijkToHex2d(&h->coord, &v); - _hex2dToGeo(&v, h->face, res, 0, g); -} - /** * Determines the center point in 3D coordinates of a cell given by * a FaceIJK address at a specified resolution. @@ -1112,17 +1066,3 @@ static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd) { } } } - -/** - * Encodes a coordinate on the sphere to the corresponding icosahedral face and - * containing the squared euclidean distance to that face center. - * - * @param g The spherical coordinates to encode. - * @param face The icosahedral face containing the spherical coordinates. - * @param sqd The squared euclidean distance to its icosahedral face center. - */ -void _geoToClosestFace(const LatLng *g, int *face, double *sqd) { - Vec3d v3d; - latLngToVec3(g, &v3d); - _vec3dToClosestFace(&v3d, face, sqd); -} diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 761cf65810..8c2a9917bd 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1044,14 +1044,9 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { return E_LATLNG_DOMAIN; } - FaceIJK fijk; - _geoToFaceIjk(g, res, &fijk); - *out = _faceIjkToH3(&fijk, res); - if (ALWAYS(*out)) { - return E_SUCCESS; - } else { - return E_FAILED; - } + Vec3d v; + latLngToVec3(g, &v); + return vec3ToCell(&v, res, out); } /** @@ -1179,12 +1174,12 @@ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { * @param g The spherical coordinates of the H3 cell center. */ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { - FaceIJK fijk; - H3Error e = _h3ToFaceIjk(h3, &fijk); + Vec3d v; + H3Error e = cellToVec3(h3, &v); if (e) { return e; } - _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); + vec3ToLatLng(&v, g); return E_SUCCESS; } diff --git a/src/h3lib/lib/latLng.c b/src/h3lib/lib/latLng.c index 082db77460..67fb1d6cf6 100644 --- a/src/h3lib/lib/latLng.c +++ b/src/h3lib/lib/latLng.c @@ -201,19 +201,6 @@ double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, const LatLng *b) { return H3_EXPORT(greatCircleDistanceKm)(a, b) * 1000; } -/** - * 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 LatLng *p1, const LatLng *p2) { - return atan2(cos(p2->lat) * sin(p2->lng - p1->lng), - cos(p1->lat) * sin(p2->lat) - - sin(p1->lat) * cos(p2->lat) * cos(p2->lng - p1->lng)); -} - /** * Computes the point on the sphere a specified azimuth and distance from * another point. diff --git a/src/h3lib/lib/vec3d.c b/src/h3lib/lib/vec3d.c index 8db1b3b372..06328d14a2 100644 --- a/src/h3lib/lib/vec3d.c +++ b/src/h3lib/lib/vec3d.c @@ -23,26 +23,6 @@ #include "constants.h" -/** - * 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. * @@ -91,3 +71,8 @@ double vec3DistSq(const Vec3d *v1, const Vec3d *v2) { } void latLngToVec3(const LatLng *geo, Vec3d *v) { _geoToVec3d(geo, v); } + +void vec3ToLatLng(const Vec3d *v, LatLng *geo) { + geo->lat = asin(v->z); + geo->lng = atan2(v->y, v->x); +} From 4aa3b63fcdbce1c0c26be999e4d7b5a12ba4d38c Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 22:12:59 -0700 Subject: [PATCH 05/91] cell boundary code to Vec3d path; remove _hex2dToGeo, _geoAzDistanceRads, and tests --- src/apps/testapps/testLatLngInternal.c | 89 -------------------------- src/h3lib/include/faceijk.h | 1 - src/h3lib/include/latLng.h | 2 - src/h3lib/lib/faceijk.c | 67 ++++--------------- src/h3lib/lib/latLng.c | 67 ------------------- 5 files changed, 12 insertions(+), 214 deletions(-) diff --git a/src/apps/testapps/testLatLngInternal.c b/src/apps/testapps/testLatLngInternal.c index fc8535c478..cdc714275c 100644 --- a/src/apps/testapps/testLatLngInternal.c +++ b/src/apps/testapps/testLatLngInternal.c @@ -65,93 +65,4 @@ SUITE(latLngInternal) { t_assert(constrainLng(3 * M_PI) == M_PI, "lng 2pi"); t_assert(constrainLng(4 * M_PI) == 0, "lng 4pi"); } - - TEST(_geoAzDistanceRads_noop) { - LatLng start = {15, 10}; - LatLng out; - LatLng expected = {15, 10}; - - _geoAzDistanceRads(&start, 0, 0, &out); - t_assert(geoAlmostEqual(&expected, &out), - "0 distance produces same point"); - } - - TEST(_geoAzDistanceRads_dueNorthSouth) { - LatLng start; - LatLng out; - LatLng expected; - - // Due north to north pole - setGeoDegs(&start, 45, 1); - setGeoDegs(&expected, 90, 0); - _geoAzDistanceRads(&start, 0, H3_EXPORT(degsToRads)(45), &out); - t_assert(geoAlmostEqual(&expected, &out), - "due north to north pole produces north pole"); - - // Due north to south pole, which doesn't get wrapped correctly - setGeoDegs(&start, 45, 1); - setGeoDegs(&expected, 270, 1); - _geoAzDistanceRads(&start, 0, H3_EXPORT(degsToRads)(45 + 180), &out); - t_assert(geoAlmostEqual(&expected, &out), - "due north to south pole produces south pole"); - - // Due south to south pole - setGeoDegs(&start, -45, 2); - setGeoDegs(&expected, -90, 0); - _geoAzDistanceRads(&start, H3_EXPORT(degsToRads)(180), - H3_EXPORT(degsToRads)(45), &out); - t_assert(geoAlmostEqual(&expected, &out), - "due south to south pole produces south pole"); - - // Due north to non-pole - setGeoDegs(&start, -45, 10); - setGeoDegs(&expected, -10, 10); - _geoAzDistanceRads(&start, 0, H3_EXPORT(degsToRads)(35), &out); - t_assert(geoAlmostEqual(&expected, &out), - "due north produces expected result"); - } - - TEST(_geoAzDistanceRads_poleToPole) { - LatLng start; - LatLng out; - LatLng expected; - - // Azimuth doesn't really matter in this case. Any azimuth from the - // north pole is south, any azimuth from the south pole is north. - - setGeoDegs(&start, 90, 0); - setGeoDegs(&expected, -90, 0); - _geoAzDistanceRads(&start, H3_EXPORT(degsToRads)(12), - H3_EXPORT(degsToRads)(180), &out); - t_assert(geoAlmostEqual(&expected, &out), - "some direction to south pole produces south pole"); - - setGeoDegs(&start, -90, 0); - setGeoDegs(&expected, 90, 0); - _geoAzDistanceRads(&start, H3_EXPORT(degsToRads)(34), - H3_EXPORT(degsToRads)(180), &out); - t_assert(geoAlmostEqual(&expected, &out), - "some direction to north pole produces north pole"); - } - - TEST(_geoAzDistanceRads_invertible) { - LatLng start; - setGeoDegs(&start, 15, 10); - LatLng out; - - double azimuth = H3_EXPORT(degsToRads)(20); - double degrees180 = H3_EXPORT(degsToRads)(180); - double distance = H3_EXPORT(degsToRads)(15); - - _geoAzDistanceRads(&start, azimuth, distance, &out); - t_assert(fabs(H3_EXPORT(greatCircleDistanceRads)(&start, &out) - - distance) < EPSILON_RAD, - "moved distance is as expected"); - - LatLng start2 = out; - _geoAzDistanceRads(&start2, azimuth + degrees180, distance, &out); - // TODO: Epsilon is relatively large - t_assert(H3_EXPORT(greatCircleDistanceRads)(&start, &out) < 0.01, - "moved back to origin"); - } } diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 452b5fc666..4b58da5fa5 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -81,7 +81,6 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); -void _hex2dToGeo(const Vec2d *v, int face, int res, int substrate, LatLng *g); Overage _adjustOverageClassII(FaceIJK *fijk, int res, int pentLeading4, int substrate); Overage _adjustPentVertOverage(FaceIJK *fijk, int res); diff --git a/src/h3lib/include/latLng.h b/src/h3lib/include/latLng.h index e340756b70..81268957de 100644 --- a/src/h3lib/include/latLng.h +++ b/src/h3lib/include/latLng.h @@ -52,7 +52,5 @@ bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, double _posAngleRads(double rads); void _setGeoRads(LatLng *p, double latRads, double lngRads); -void _geoAzDistanceRads(const LatLng *p1, double az, double distance, - LatLng *p2); #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index a1fa50d321..bd07d6f928 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -541,55 +541,6 @@ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, vec3Normalize(v3d); } -/** - * 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, LatLng *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_RSQRT7; - - // scale accordingly if this is a substrate grid - if (substrate) { - r *= M_ONETHIRD; - if (isResolutionClassIII(res)) r *= M_RSQRT7; - } - - 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 && isResolutionClassIII(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 3D coordinates of a cell given by * a FaceIJK address at a specified resolution. @@ -696,8 +647,9 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // find the intersection and add the lat/lng point to the result Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - _hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, - &g->verts[g->numVerts]); + Vec3d v3d; + _hex2dToVec3(&inter, tmpFijk.face, adjRes, 1, &v3d); + vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } @@ -707,7 +659,9 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, if (vert < start + NUM_PENT_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); - _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); + Vec3d v3d; + _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); + vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } @@ -869,8 +823,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, bool isIntersectionAtVertex = _v2dAlmostEquals(&orig2d0, &inter) || _v2dAlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { - _hex2dToGeo(&inter, centerIJK.face, adjRes, 1, - &g->verts[g->numVerts]); + Vec3d v3d; + _hex2dToVec3(&inter, centerIJK.face, adjRes, 1, &v3d); + vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } } @@ -881,7 +836,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, if (vert < start + NUM_HEX_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); - _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); + Vec3d v3d; + _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); + vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } diff --git a/src/h3lib/lib/latLng.c b/src/h3lib/lib/latLng.c index 67fb1d6cf6..c2bf19045c 100644 --- a/src/h3lib/lib/latLng.c +++ b/src/h3lib/lib/latLng.c @@ -201,73 +201,6 @@ double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, const LatLng *b) { return H3_EXPORT(greatCircleDistanceKm)(a, b) * 1000; } -/** - * 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 LatLng *p1, double az, double distance, - LatLng *p2) { - if (distance < EPSILON) { - *p2 = *p1; - return; - } - - double sinlat, sinlng, coslng; - - 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->lng = 0.0; - } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole - { - p2->lat = -M_PI_2; - p2->lng = 0.0; - } else - p2->lng = constrainLng(p1->lng); - } else // not due north or south - { - sinlat = sin(p1->lat) * cos(distance) + - cos(p1->lat) * sin(distance) * cos(az); - if (sinlat > 1.0) sinlat = 1.0; - if (sinlat < -1.0) sinlat = -1.0; - p2->lat = asin(sinlat); - if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole - { - p2->lat = M_PI_2; - p2->lng = 0.0; - } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole - { - p2->lat = -M_PI_2; - p2->lng = 0.0; - } else { - double invcosp2lat = 1.0 / cos(p2->lat); - sinlng = sin(az) * sin(distance) * invcosp2lat; - coslng = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / - cos(p1->lat) * invcosp2lat; - if (sinlng > 1.0) sinlng = 1.0; - if (sinlng < -1.0) sinlng = -1.0; - if (coslng > 1.0) coslng = 1.0; - if (coslng < -1.0) coslng = -1.0; - p2->lng = constrainLng(p1->lng + atan2(sinlng, coslng)); - } - } -} - /* * The following functions provide meta information about the H3 hexagons at * each zoom level. Since there are only 16 total levels, these are current From 0c5dcbad826966eb46fb1a38d60652a652428ad6 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 22:21:23 -0700 Subject: [PATCH 06/91] vec3ToCell validation tests --- src/apps/testapps/testVec3d.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/apps/testapps/testVec3d.c b/src/apps/testapps/testVec3d.c index 6244aae5f4..5bb10a089f 100644 --- a/src/apps/testapps/testVec3d.c +++ b/src/apps/testapps/testVec3d.c @@ -21,6 +21,7 @@ #include #include "constants.h" +#include "h3Index.h" #include "test.h" #include "vec3d.h" @@ -81,4 +82,25 @@ SUITE(Vec3d) { t_assert(fabs(vec3Mag(&viaReturn) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } + + TEST(vec3ToCell_invalidRes) { + Vec3d v = {.x = 1.0, .y = 0.0, .z = 0.0}; + H3Index out; + t_assert(vec3ToCell(&v, -1, &out) == E_RES_DOMAIN, + "negative resolution is rejected"); + t_assert(vec3ToCell(&v, 16, &out) == E_RES_DOMAIN, + "resolution above max is rejected"); + } + + TEST(vec3ToCell_nonFinite) { + H3Index out; + Vec3d nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; + t_assert(vec3ToCell(&nan_x, 0, &out) == E_DOMAIN, "NaN x is rejected"); + Vec3d inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; + t_assert(vec3ToCell(&inf_y, 0, &out) == E_DOMAIN, + "infinite y is rejected"); + Vec3d inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; + t_assert(vec3ToCell(&inf_z, 0, &out) == E_DOMAIN, + "infinite z is rejected"); + } } From 6c3eedf3e32fd11db1031793b686f6c27f432c08 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 22:48:40 -0700 Subject: [PATCH 07/91] align function names and struct: hex2d -> vec2d --- src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 6 ++--- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/vec2d.h | 8 +++++-- src/h3lib/lib/coordijk.c | 4 ++-- src/h3lib/lib/faceijk.c | 42 +++++++++++++++++------------------ 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index 90b7036aa0..becfb3b30f 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -66,7 +66,7 @@ /** earth radius in kilometers using WGS84 authalic radius */ #define EARTH_RADIUS_KM 6371.007180918475 -/** scaling factor from hex2d resolution 0 unit length +/** scaling factor from Vec2d resolution 0 unit length * (or distance between adjacent cell center points * on the plane) to gnomonic unit length. */ #define RES0_U_GNOMONIC 0.38196601125010500003 diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 422fdb1c10..ea508bb0b4 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -22,7 +22,7 @@ * 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 + * 2. Vec2d: local face-centered coordinate system scaled a specific H3 grid * resolution unit length and with x-axes aligned with the local * i-axes */ @@ -87,8 +87,8 @@ typedef enum { // 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); +void _vec2dToCoordIJK(const Vec2d *v, CoordIJK *h); +void _ijkToVec2d(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); diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 4b58da5fa5..4562412e1f 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -16,7 +16,7 @@ /** @file faceijk.h * @brief FaceIJK functions including conversion to/from lat/lng. * - * References the Vec2d cartesian coordinate systems hex2d: local face-centered + * References the Vec2d cartesian coordinate system: local face-centered * coordinate system scaled a specific H3 grid resolution unit length and * with x-axes aligned with the local i-axes */ diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2d.h index 8ba0e9bd4e..abf3c5d7e6 100644 --- a/src/h3lib/include/vec2d.h +++ b/src/h3lib/include/vec2d.h @@ -24,10 +24,14 @@ /** @struct Vec2d * @brief 2D floating-point vector + * + * Represents a point in the face-local Vec2d coordinate system: + * an orthogonal 2D plane centered on an icosahedron face, with the + * x-axis aligned to the face's i-axis and y perpendicular to it. */ typedef struct { - double x; ///< x component - double y; ///< y component + double x; ///< x component (aligned with face i-axis) + double y; ///< y component (perpendicular to face i-axis) } Vec2d; // Internal functions diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 0bed0250b5..94bcacac11 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -53,7 +53,7 @@ void _setIJK(CoordIJK *ijk, int i, int j, int k) { * @param v The 2D cartesian coordinate vector. * @param h The ijk+ coordinates of the containing hex. */ -void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) { +void _vec2dToCoordIJK(const Vec2d *v, CoordIJK *h) { double a1, a2; double x1, x2; int m1, m2; @@ -152,7 +152,7 @@ void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) { * @param h The ijk coordinates of the hex. * @param v The 2D cartesian coordinates of the hex center point. */ -void _ijkToHex2d(const CoordIJK *h, Vec2d *v) { +void _ijkToVec2d(const CoordIJK *h, Vec2d *v) { int i = h->i - h->k; int j = h->j - h->k; diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index bd07d6f928..7c99ed6226 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -407,7 +407,7 @@ static double _vec3dAzimuthRads(const Vec3d *p1, const Vec3d *p2) { * @param face The icosahedral face containing the spherical coordinates. * @param v The 2D hex coordinates of the cell containing the point. */ -static void _vec3dToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { +static void _vec3dToVec2d(const Vec3d *p, int res, int *face, Vec2d *v) { // determine the icosahedron face double sqd; _vec3dToClosestFace(p, face, &sqd); @@ -436,7 +436,7 @@ static void _vec3dToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { r *= INV_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 + // we now have (r, theta) in Vec2d with theta ccw from x-axes // convert to local x,y v->x = r * cos(theta); @@ -452,12 +452,12 @@ static void _vec3dToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { * @param h The FaceIJK address of the containing cell at resolution res. */ void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { - // first convert to hex2d + // first convert to Vec2d Vec2d v; - _vec3dToHex2d(p, res, &h->face, &v); + _vec3dToVec2d(p, res, &h->face, &v); // then convert to ijk+ - _hex2dToCoordIJK(&v, &h->coord); + _vec2dToCoordIJK(&v, &h->coord); } /** @@ -472,9 +472,9 @@ void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { * grid relative to the specified resolution. * @param v3d The 3D coordinates of the cell center point. */ -void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, +void _vec2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3d) { - // calculate (r, theta) in hex2d + // calculate (r, theta) in Vec2d double r = _v2dMag(v); if (r < EPSILON) { @@ -551,8 +551,8 @@ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, */ void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d) { Vec2d v; - _ijkToHex2d(&h->coord, &v); - _hex2dToVec3(&v, h->face, res, 0, v3d); + _ijkToVec2d(&h->coord, &v); + _vec2dToVec3(&v, h->face, res, 0, v3d); } /** @@ -593,12 +593,12 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // note that Class II pentagons have vertices on the edge, // not edge intersections if (isResolutionClassIII(res) && vert > start) { - // find hex2d of the two vertexes on the last face + // find Vec2d of the two vertexes on the last face FaceIJK tmpFijk = fijk; Vec2d orig2d0; - _ijkToHex2d(&lastFijk.coord, &orig2d0); + _ijkToVec2d(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -617,7 +617,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkNormalize(ijk); Vec2d orig2d1; - _ijkToHex2d(ijk, &orig2d1); + _ijkToVec2d(ijk, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -648,7 +648,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); Vec3d v3d; - _hex2dToVec3(&inter, tmpFijk.face, adjRes, 1, &v3d); + _vec2dToVec3(&inter, tmpFijk.face, adjRes, 1, &v3d); vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } @@ -658,9 +658,9 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // intersection on last edge if (vert < start + NUM_PENT_VERTS) { Vec2d vec; - _ijkToHex2d(&fijk.coord, &vec); + _ijkToVec2d(&fijk.coord, &vec); Vec3d v3d; - _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); + _vec2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } @@ -778,13 +778,13 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, */ if (isResolutionClassIII(res) && vert > start && fijk.face != lastFace && lastOverage != FACE_EDGE) { - // find hex2d of the two vertexes on original face + // find Vec2d of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; Vec2d orig2d0; - _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); + _ijkToVec2d(&fijkVerts[lastV].coord, &orig2d0); Vec2d orig2d1; - _ijkToHex2d(&fijkVerts[v].coord, &orig2d1); + _ijkToVec2d(&fijkVerts[v].coord, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -824,7 +824,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, _v2dAlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { Vec3d v3d; - _hex2dToVec3(&inter, centerIJK.face, adjRes, 1, &v3d); + _vec2dToVec3(&inter, centerIJK.face, adjRes, 1, &v3d); vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } @@ -835,9 +835,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // intersection on last edge if (vert < start + NUM_HEX_VERTS) { Vec2d vec; - _ijkToHex2d(&fijk.coord, &vec); + _ijkToVec2d(&fijk.coord, &vec); Vec3d v3d; - _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); + _vec2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); vec3ToLatLng(&v3d, &g->verts[g->numVerts]); g->numVerts++; } From 2f159ed64039317a3b5a55ed8e2098f122485bcb Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 23:09:42 -0700 Subject: [PATCH 08/91] use Vec2 and Vec3 everywhere --- CMakeLists.txt | 14 +- CMakeTests.cmake | 6 +- src/apps/miscapps/generateFaceCenterPoint.c | 8 +- .../testapps/testCellToBoundaryEdgeCases.c | 2 +- ...testVec2dInternal.c => testVec2Internal.c} | 46 ++--- src/apps/testapps/{testVec3d.c => testVec3.c} | 51 ++--- ...testVec3dInternal.c => testVec3Internal.c} | 20 +- src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 10 +- src/h3lib/include/faceijk.h | 10 +- src/h3lib/include/h3Index.h | 4 +- src/h3lib/include/{vec2d.h => vec2.h} | 20 +- src/h3lib/include/{vec3d.h => vec3.h} | 31 ++- src/h3lib/lib/coordijk.c | 4 +- src/h3lib/lib/faceijk.c | 181 +++++++++--------- src/h3lib/lib/h3Index.c | 10 +- src/h3lib/lib/{vec2d.c => vec2.c} | 14 +- src/h3lib/lib/{vec3d.c => vec3.c} | 22 +-- 18 files changed, 221 insertions(+), 234 deletions(-) rename src/apps/testapps/{testVec2dInternal.c => testVec2Internal.c} (55%) rename src/apps/testapps/{testVec3d.c => testVec3.c} (66%) rename src/apps/testapps/{testVec3dInternal.c => testVec3Internal.c} (82%) rename src/h3lib/include/{vec2d.h => vec2.h} (74%) rename src/h3lib/include/{vec3d.h => vec3.h} (61%) rename src/h3lib/lib/{vec2d.c => vec2.c} (86%) rename src/h3lib/lib/{vec3d.c => vec3.c} (76%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 43fbc12c7d..adbb8e56f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,8 +157,8 @@ set(LIB_SOURCE_FILES src/h3lib/include/h3Index.h src/h3lib/include/directedEdge.h src/h3lib/include/latLng.h - src/h3lib/include/vec2d.h - src/h3lib/include/vec3d.h + src/h3lib/include/vec2.h + src/h3lib/include/vec3.h src/h3lib/include/linkedGeo.h src/h3lib/include/localij.h src/h3lib/include/baseCells.h @@ -179,8 +179,8 @@ set(LIB_SOURCE_FILES src/h3lib/lib/polygon.c src/h3lib/lib/polyfill.c src/h3lib/lib/h3Index.c - src/h3lib/lib/vec2d.c - src/h3lib/lib/vec3d.c + src/h3lib/lib/vec2.c + src/h3lib/lib/vec3.c src/h3lib/lib/vertex.c src/h3lib/lib/linkedGeo.c src/h3lib/lib/localij.c @@ -253,9 +253,9 @@ set(OTHER_SOURCE_FILES src/apps/testapps/testVertexExhaustive.c src/apps/testapps/testPolygonInternal.c src/apps/testapps/testPolyfillInternal.c - src/apps/testapps/testVec2dInternal.c - src/apps/testapps/testVec3dInternal.c - src/apps/testapps/testVec3d.c + src/apps/testapps/testVec2Internal.c + src/apps/testapps/testVec3Internal.c + src/apps/testapps/testVec3.c src/apps/testapps/testDirectedEdge.c src/apps/testapps/testDirectedEdgeExhaustive.c src/apps/testapps/testLinkedGeoInternal.c diff --git a/CMakeTests.cmake b/CMakeTests.cmake index 13bfdc6047..e5352c8f3e 100644 --- a/CMakeTests.cmake +++ b/CMakeTests.cmake @@ -240,9 +240,9 @@ add_h3_test(testVertex src/apps/testapps/testVertex.c) add_h3_test(testVertexInternal src/apps/testapps/testVertexInternal.c) add_h3_test(testPolygonInternal src/apps/testapps/testPolygonInternal.c) add_h3_test(testPolyfillInternal src/apps/testapps/testPolyfillInternal.c) -add_h3_test(testVec2dInternal src/apps/testapps/testVec2dInternal.c) -add_h3_test(testVec3dInternal src/apps/testapps/testVec3dInternal.c) -add_h3_test(testVec3d src/apps/testapps/testVec3d.c) +add_h3_test(testVec2Internal src/apps/testapps/testVec2Internal.c) +add_h3_test(testVec3Internal src/apps/testapps/testVec3Internal.c) +add_h3_test(testVec3 src/apps/testapps/testVec3.c) add_h3_test(testCellToLocalIj src/apps/testapps/testCellToLocalIj.c) add_h3_test(testCellToLocalIjInternal src/apps/testapps/testCellToLocalIjInternal.c) diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index 027c6e63c9..e9f1daf027 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -22,7 +22,7 @@ #include #include "faceijk.h" -#include "vec3d.h" +#include "vec3.h" /** @brief icosahedron face centers in lat/lng radians. Copied from faceijk.c. */ @@ -53,11 +53,11 @@ const LatLng faceCenterGeoCopy[NUM_ICOSA_FACES] = { * Generates and prints the faceCenterPoint table. */ static void generate(void) { - printf("static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = {\n"); + printf("static const Vec3 faceCenterPoint[NUM_ICOSA_FACES] = {\n"); for (int i = 0; i < NUM_ICOSA_FACES; i++) { LatLng centerCoords = faceCenterGeoCopy[i]; - Vec3d centerPoint; - _geoToVec3d(¢erCoords, ¢erPoint); + Vec3 centerPoint; + latLngToVec3(¢erCoords, ¢erPoint); printf(" {%.16f, %.16f, %.16f}, // face %2d\n", centerPoint.x, centerPoint.y, centerPoint.z, i); } diff --git a/src/apps/testapps/testCellToBoundaryEdgeCases.c b/src/apps/testapps/testCellToBoundaryEdgeCases.c index f7eca1742f..3289bf7e1c 100644 --- a/src/apps/testapps/testCellToBoundaryEdgeCases.c +++ b/src/apps/testapps/testCellToBoundaryEdgeCases.c @@ -28,7 +28,7 @@ SUITE(cellToBoundaryEdgeCases) { TEST(doublePrecisionVertex) { // The carefully constructed case here: // - A res 1 pentagon cell with distortion vertexes that change - // when we use a double instead of a float in _v2dIntersect + // when we use a double instead of a float in _vec2Intersect // - One of the previous (float-based) distortion vertexes // This is the only case yet found where a point indexed to the // cell is shown to be incorrectly outside of the geo boundary diff --git a/src/apps/testapps/testVec2dInternal.c b/src/apps/testapps/testVec2Internal.c similarity index 55% rename from src/apps/testapps/testVec2dInternal.c rename to src/apps/testapps/testVec2Internal.c index e1e284bd23..20bc0b3d5f 100644 --- a/src/apps/testapps/testVec2dInternal.c +++ b/src/apps/testapps/testVec2Internal.c @@ -19,24 +19,24 @@ #include #include "test.h" -#include "vec2d.h" +#include "vec2.h" -SUITE(Vec2dInternal) { - TEST(_v2dMag) { - Vec2d v = {3.0, 4.0}; +SUITE(Vec2Internal) { + TEST(_vec2Mag) { + Vec2 v = {3.0, 4.0}; double expected = 5.0; - double mag = _v2dMag(&v); + double mag = _vec2Mag(&v); t_assert(fabs(mag - expected) < DBL_EPSILON, "magnitude as expected"); } - TEST(_v2dIntersect) { - Vec2d p0 = {2.0, 2.0}; - Vec2d p1 = {6.0, 6.0}; - Vec2d p2 = {0.0, 4.0}; - Vec2d p3 = {10.0, 4.0}; - Vec2d intersection = {0.0, 0.0}; + TEST(_vec2Intersect) { + Vec2 p0 = {2.0, 2.0}; + Vec2 p1 = {6.0, 6.0}; + Vec2 p2 = {0.0, 4.0}; + Vec2 p3 = {10.0, 4.0}; + Vec2 intersection = {0.0, 0.0}; - _v2dIntersect(&p0, &p1, &p2, &p3, &intersection); + _vec2Intersect(&p0, &p1, &p2, &p3, &intersection); double expectedX = 4.0; double expectedY = 4.0; @@ -47,16 +47,16 @@ SUITE(Vec2dInternal) { "Y coord as expected"); } - TEST(_v2dAlmostEquals) { - Vec2d v1 = {3.0, 4.0}; - Vec2d v2 = {3.0, 4.0}; - Vec2d v3 = {3.5, 4.0}; - Vec2d v4 = {3.0, 4.5}; - Vec2d v5 = {3.0 + DBL_EPSILON, 4.0 - DBL_EPSILON}; - - t_assert(_v2dAlmostEquals(&v1, &v2), "true for equal vectors"); - t_assert(!_v2dAlmostEquals(&v1, &v3), "false for different x"); - t_assert(!_v2dAlmostEquals(&v1, &v4), "false for different y"); - t_assert(_v2dAlmostEquals(&v1, &v5), "true for almost equal"); + TEST(_vec2AlmostEquals) { + Vec2 v1 = {3.0, 4.0}; + Vec2 v2 = {3.0, 4.0}; + Vec2 v3 = {3.5, 4.0}; + Vec2 v4 = {3.0, 4.5}; + Vec2 v5 = {3.0 + DBL_EPSILON, 4.0 - DBL_EPSILON}; + + t_assert(_vec2AlmostEquals(&v1, &v2), "true for equal vectors"); + t_assert(!_vec2AlmostEquals(&v1, &v3), "false for different x"); + t_assert(!_vec2AlmostEquals(&v1, &v4), "false for different y"); + t_assert(_vec2AlmostEquals(&v1, &v5), "true for almost equal"); } } diff --git a/src/apps/testapps/testVec3d.c b/src/apps/testapps/testVec3.c similarity index 66% rename from src/apps/testapps/testVec3d.c rename to src/apps/testapps/testVec3.c index 5bb10a089f..ba915d2b34 100644 --- a/src/apps/testapps/testVec3d.c +++ b/src/apps/testapps/testVec3.c @@ -14,8 +14,8 @@ * limitations under the License. */ -/** @file testVec3d.c - * @brief Tests the vec3d helpers used by the geodesic polyfill path. +/** @file testVec3.c + * @brief Tests the Vec3 helpers used by the geodesic polyfill path. */ #include @@ -23,19 +23,19 @@ #include "constants.h" #include "h3Index.h" #include "test.h" -#include "vec3d.h" +#include "vec3.h" -SUITE(Vec3d) { +SUITE(Vec3) { TEST(dotProduct) { - Vec3d a = {.x = 1.0, .y = 0.0, .z = 0.0}; - Vec3d b = {.x = -1.0, .y = 0.0, .z = 0.0}; + Vec3 a = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3 b = {.x = -1.0, .y = 0.0, .z = 0.0}; t_assert(vec3Dot(&a, &b) == -1.0, "dot product matches expected value"); } TEST(crossProductOrthogonality) { - Vec3d i = {.x = 1.0, .y = 0.0, .z = 0.0}; - Vec3d j = {.x = 0.0, .y = 1.0, .z = 0.0}; - Vec3d k; + Vec3 i = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3 j = {.x = 0.0, .y = 1.0, .z = 0.0}; + Vec3 k; vec3Cross(&i, &j, &k); t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); @@ -45,7 +45,7 @@ SUITE(Vec3d) { } TEST(normalizeAndMagnitude) { - Vec3d v = {.x = 3.0, .y = -4.0, .z = 12.0}; + Vec3 v = {.x = 3.0, .y = -4.0, .z = 12.0}; double magSq = vec3MagSq(&v); t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); t_assert(fabs(vec3Mag(&v) - 13.0) < EPSILON, "magnitude matches"); @@ -53,38 +53,29 @@ SUITE(Vec3d) { vec3Normalize(&v); t_assert(fabs(vec3Mag(&v) - 1.0) < 1e-12, "normalized vector is unit"); - Vec3d zero = {.x = 0.0, .y = 0.0, .z = 0.0}; + Vec3 zero = {.x = 0.0, .y = 0.0, .z = 0.0}; vec3Normalize(&zero); t_assert(zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0, "zero vector remains unchanged when normalizing"); } TEST(distance) { - Vec3d a = {.x = 0.0, .y = 0.0, .z = 0.0}; - Vec3d b = {.x = 1.0, .y = 2.0, .z = 2.0}; + Vec3 a = {.x = 0.0, .y = 0.0, .z = 0.0}; + Vec3 b = {.x = 1.0, .y = 2.0, .z = 2.0}; t_assert(fabs(vec3DistSq(&a, &b) - 9.0) < EPSILON, "distance squared matches"); } - TEST(latLngConversionConsistency) { + TEST(latLngToVec3_unitSphere) { LatLng geo = {.lat = 0.5, .lng = -1.3}; - Vec3d viaReturn; - latLngToVec3(&geo, &viaReturn); - Vec3d viaOut; - _geoToVec3d(&geo, &viaOut); - - t_assert(fabs(viaReturn.x - viaOut.x) < EPSILON, - "x coordinate consistent"); - t_assert(fabs(viaReturn.y - viaOut.y) < EPSILON, - "y coordinate consistent"); - t_assert(fabs(viaReturn.z - viaOut.z) < EPSILON, - "z coordinate consistent"); - t_assert(fabs(vec3Mag(&viaReturn) - 1.0) < 1e-12, + Vec3 v; + latLngToVec3(&geo, &v); + t_assert(fabs(vec3Mag(&v) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } TEST(vec3ToCell_invalidRes) { - Vec3d v = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3 v = {.x = 1.0, .y = 0.0, .z = 0.0}; H3Index out; t_assert(vec3ToCell(&v, -1, &out) == E_RES_DOMAIN, "negative resolution is rejected"); @@ -94,12 +85,12 @@ SUITE(Vec3d) { TEST(vec3ToCell_nonFinite) { H3Index out; - Vec3d nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; + Vec3 nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; t_assert(vec3ToCell(&nan_x, 0, &out) == E_DOMAIN, "NaN x is rejected"); - Vec3d inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; + Vec3 inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; t_assert(vec3ToCell(&inf_y, 0, &out) == E_DOMAIN, "infinite y is rejected"); - Vec3d inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; + Vec3 inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; t_assert(vec3ToCell(&inf_z, 0, &out) == E_DOMAIN, "infinite z is rejected"); } diff --git a/src/apps/testapps/testVec3dInternal.c b/src/apps/testapps/testVec3Internal.c similarity index 82% rename from src/apps/testapps/testVec3dInternal.c rename to src/apps/testapps/testVec3Internal.c index b7c4232f40..8b98737870 100644 --- a/src/apps/testapps/testVec3dInternal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -17,27 +17,27 @@ #include #include "test.h" -#include "vec3d.h" +#include "vec3.h" -SUITE(Vec3dInternal) { - TEST(_geoToVec3d) { - Vec3d origin = {0}; +SUITE(Vec3Internal) { + TEST(latLngToVec3) { + Vec3 origin = {0}; LatLng c1 = {0, 0}; - Vec3d p1; - _geoToVec3d(&c1, &p1); + Vec3 p1; + latLngToVec3(&c1, &p1); t_assert(fabs(vec3DistSq(&origin, &p1) - 1) < EPSILON_RAD, "Geo point is on the unit sphere"); LatLng c2 = {M_PI_2, 0}; - Vec3d p2; - _geoToVec3d(&c2, &p2); + Vec3 p2; + latLngToVec3(&c2, &p2); t_assert(fabs(vec3DistSq(&p1, &p2) - 2) < EPSILON_RAD, "Geo point is on another axis"); LatLng c3 = {M_PI, 0}; - Vec3d p3; - _geoToVec3d(&c3, &p3); + Vec3 p3; + latLngToVec3(&c3, &p3); t_assert(fabs(vec3DistSq(&p1, &p3) - 4) < EPSILON_RAD, "Geo point is the other side of the sphere"); } diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index becfb3b30f..d536facb1d 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -66,7 +66,7 @@ /** earth radius in kilometers using WGS84 authalic radius */ #define EARTH_RADIUS_KM 6371.007180918475 -/** scaling factor from Vec2d resolution 0 unit length +/** scaling factor from Vec2 resolution 0 unit length * (or distance between adjacent cell center points * on the plane) to gnomonic unit length. */ #define RES0_U_GNOMONIC 0.38196601125010500003 diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index ea508bb0b4..0beb53fb5c 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -16,13 +16,13 @@ /** @file coordijk.h * @brief Header file for CoordIJK functions including conversion from lat/lng * - * References two Vec2d cartesian coordinate systems: + * References two Vec2 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. Vec2d: local face-centered coordinate system scaled a specific H3 grid + * 2. Vec2: local face-centered coordinate system scaled a specific H3 grid * resolution unit length and with x-axes aligned with the local * i-axes */ @@ -32,7 +32,7 @@ #include "h3api.h" #include "latLng.h" -#include "vec2d.h" +#include "vec2.h" /** @struct CoordIJK * @brief IJK hexagon coordinates @@ -87,8 +87,8 @@ typedef enum { // Internal functions void _setIJK(CoordIJK *ijk, int i, int j, int k); -void _vec2dToCoordIJK(const Vec2d *v, CoordIJK *h); -void _ijkToVec2d(const CoordIJK *h, Vec2d *v); +void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); +void _ijkToVec2(const CoordIJK *h, Vec2 *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); diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 4562412e1f..78474f348b 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -16,7 +16,7 @@ /** @file faceijk.h * @brief FaceIJK functions including conversion to/from lat/lng. * - * References the Vec2d cartesian coordinate system: local face-centered + * References the Vec2 cartesian coordinate system: local face-centered * coordinate system scaled a specific H3 grid resolution unit length and * with x-axes aligned with the local i-axes */ @@ -26,8 +26,8 @@ #include "coordijk.h" #include "latLng.h" -#include "vec2d.h" -#include "vec3d.h" +#include "vec2.h" +#include "vec3.h" /** @struct FaceIJK * @brief Face number and ijk coordinates on that face-centered coordinate @@ -73,8 +73,8 @@ typedef enum { // Internal functions -void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h); -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d); +void _vec3ToFaceIjk(const Vec3 *p, int res, FaceIJK *h); +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index 3ef3e9ea48..ff015f9d0e 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -179,7 +179,7 @@ H3Index _h3Rotate60ccw(H3Index h); H3Index _h3Rotate60cw(H3Index h); DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); -H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out); -H3Error cellToVec3(H3Index h3, Vec3d *v); +H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out); +H3Error cellToVec3(H3Index h3, Vec3 *v); #endif diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2.h similarity index 74% rename from src/h3lib/include/vec2d.h rename to src/h3lib/include/vec2.h index abf3c5d7e6..5dde602a7b 100644 --- a/src/h3lib/include/vec2d.h +++ b/src/h3lib/include/vec2.h @@ -13,32 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec2d.h +/** @file vec2.h * @brief 2D floating point vector functions. */ -#ifndef VEC2D_H -#define VEC2D_H +#ifndef VEC2_H +#define VEC2_H #include -/** @struct Vec2d +/** @struct Vec2 * @brief 2D floating-point vector * - * Represents a point in the face-local Vec2d coordinate system: + * Represents a point in the face-local Vec2 coordinate system: * an orthogonal 2D plane centered on an icosahedron face, with the * x-axis aligned to the face's i-axis and y perpendicular to it. */ typedef struct { double x; ///< x component (aligned with face i-axis) double y; ///< y component (perpendicular to face i-axis) -} Vec2d; +} Vec2; // Internal functions -double _v2dMag(const Vec2d *v); -void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, - const Vec2d *p3, Vec2d *inter); -bool _v2dAlmostEquals(const Vec2d *p0, const Vec2d *p1); +double _vec2Mag(const Vec2 *v); +void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, + const Vec2 *p3, Vec2 *inter); +bool _vec2AlmostEquals(const Vec2 *p0, const Vec2 *p1); #endif diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3.h similarity index 61% rename from src/h3lib/include/vec3d.h rename to src/h3lib/include/vec3.h index cb1da11837..1202da13a9 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3.h @@ -13,36 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec3d.h +/** @file vec3.h * @brief 3D floating point vector functions. */ -#ifndef VEC3D_H -#define VEC3D_H +#ifndef VEC3_H +#define VEC3_H #include "h3api.h" #include "latLng.h" -/** @struct Vec3d +/** @struct Vec3 * @brief 3D floating point structure * * For geodesic calulations represents a point on the surface of the Earth * as a unit vector in 3D Cartesian space (ECEF-like coordinates). */ typedef struct { - double x; ///< x component (towards 0° lat, 0° lon) - double y; ///< y component (towards 0° lat, 90° lon) + double x; ///< x component (towards 0deg lat, 0deg lon) + double y; ///< y component (towards 0deg lat, 90deg lon) double z; ///< z component (towards north pole) -} Vec3d; +} Vec3; -void _geoToVec3d(const LatLng *geo, Vec3d *point); -double vec3Dot(const Vec3d *v1, const Vec3d *v2); -void vec3Cross(const Vec3d *v1, const Vec3d *v2, Vec3d *out); -void vec3Normalize(Vec3d *v); -double vec3MagSq(const Vec3d *v); -double vec3Mag(const Vec3d *v); -double vec3DistSq(const Vec3d *v1, const Vec3d *v2); -void latLngToVec3(const LatLng *geo, Vec3d *v); -void vec3ToLatLng(const Vec3d *v, LatLng *geo); +double vec3Dot(const Vec3 *v1, const Vec3 *v2); +void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out); +void vec3Normalize(Vec3 *v); +double vec3MagSq(const Vec3 *v); +double vec3Mag(const Vec3 *v); +double vec3DistSq(const Vec3 *v1, const Vec3 *v2); +void latLngToVec3(const LatLng *geo, Vec3 *v); +void vec3ToLatLng(const Vec3 *v, LatLng *geo); #endif diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 94bcacac11..34162440b6 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -53,7 +53,7 @@ void _setIJK(CoordIJK *ijk, int i, int j, int k) { * @param v The 2D cartesian coordinate vector. * @param h The ijk+ coordinates of the containing hex. */ -void _vec2dToCoordIJK(const Vec2d *v, CoordIJK *h) { +void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { double a1, a2; double x1, x2; int m1, m2; @@ -152,7 +152,7 @@ void _vec2dToCoordIJK(const Vec2d *v, CoordIJK *h) { * @param h The ijk coordinates of the hex. * @param v The 2D cartesian coordinates of the hex center point. */ -void _ijkToVec2d(const CoordIJK *h, Vec2d *v) { +void _ijkToVec2(const CoordIJK *h, Vec2 *v) { int i = h->i - h->k; int j = h->j - h->k; diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 7c99ed6226..9b40ddd76f 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -30,7 +30,7 @@ #include "coordijk.h" #include "h3Index.h" #include "latLng.h" -#include "vec3d.h" +#include "vec3.h" /** square root of 7 and inverse square root of 7 */ #define M_SQRT7 2.6457513110645905905016157536392604257102 @@ -61,7 +61,7 @@ const LatLng faceCenterGeo[NUM_ICOSA_FACES] = { }; /** @brief icosahedron face centers in x/y/z on the unit sphere */ -static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = { +static const Vec3 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 @@ -362,7 +362,7 @@ static const int unitScaleByCIIres[] = { }; // Private function declaration -static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd); +static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); /** * Calculates the azimuth from p1 to p2. @@ -370,21 +370,21 @@ static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd); * @param p2 The second vector. * @return The azimuth in radians. */ -static double _vec3dAzimuthRads(const Vec3d *p1, const Vec3d *p2) { - Vec3d northPole = {0.0, 0.0, 1.0}; +static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { + Vec3 northPole = {0.0, 0.0, 1.0}; // local north direction on tangent plane. double NdotC = vec3Dot(&northPole, p1); - Vec3d northDir = {northPole.x - NdotC * p1->x, northPole.y - NdotC * p1->y, - northPole.z - NdotC * p1->z}; + Vec3 northDir = {northPole.x - NdotC * p1->x, northPole.y - NdotC * p1->y, + northPole.z - NdotC * p1->z}; vec3Normalize(&northDir); // local east direction on tangent plane - Vec3d eastDir; + Vec3 eastDir; vec3Cross(&northDir, p1, &eastDir); // vector from p1 to p2 on tangent plane - Vec3d p2_on_tangent; + Vec3 p2_on_tangent; double p2dotp1 = vec3Dot(p2, p1); p2_on_tangent.x = p2->x - p2dotp1 * p1->x; p2_on_tangent.y = p2->y - p2dotp1 * p1->y; @@ -402,15 +402,15 @@ static double _vec3dAzimuthRads(const Vec3d *p1, const Vec3d *p2) { * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing 2D hex coordinates relative to that face center. * - * @param p The Vec3d coordinates to encode. + * @param p The Vec3 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. */ -static void _vec3dToVec2d(const Vec3d *p, int res, int *face, Vec2d *v) { +static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { // determine the icosahedron face double sqd; - _vec3dToClosestFace(p, face, &sqd); + _vec3ToClosestFace(p, face, &sqd); // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 double r = acos(1 - sqd * 0.5); @@ -421,7 +421,7 @@ static void _vec3dToVec2d(const Vec3d *p, int res, int *face, Vec2d *v) { } // now have face and r, now find CCW theta from CII i-axis - double p_az = _vec3dAzimuthRads(&faceCenterPoint[*face], p); + double p_az = _vec3AzimuthRads(&faceCenterPoint[*face], p); double theta = _posAngleRads(faceAxesAzRadsCII[*face][0] - _posAngleRads(p_az)); @@ -436,7 +436,7 @@ static void _vec3dToVec2d(const Vec3d *p, int res, int *face, Vec2d *v) { r *= INV_RES0_U_GNOMONIC; for (int i = 0; i < res; i++) r *= M_SQRT7; - // we now have (r, theta) in Vec2d with theta ccw from x-axes + // we now have (r, theta) in Vec2 with theta ccw from x-axes // convert to local x,y v->x = r * cos(theta); @@ -444,20 +444,20 @@ static void _vec3dToVec2d(const Vec3d *p, int res, int *face, Vec2d *v) { } /** - * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at + * Encodes a Vec3 coordinate to the FaceIJK address of the containing cell at * the specified resolution. * - * @param p The Vec3d coordinates to encode. + * @param p The Vec3 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 _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { - // first convert to Vec2d - Vec2d v; - _vec3dToVec2d(p, res, &h->face, &v); +void _vec3ToFaceIjk(const Vec3 *p, int res, FaceIJK *h) { + // first convert to Vec2 + Vec2 v; + _vec3ToVec2(p, res, &h->face, &v); // then convert to ijk+ - _vec2dToCoordIJK(&v, &h->coord); + _vec2ToCoordIJK(&v, &h->coord); } /** @@ -470,15 +470,14 @@ void _vec3dToFaceIjk(const Vec3d *p, int res, FaceIJK *h) { * @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 v3d The 3D coordinates of the cell center point. + * @param v3 The 3D coordinates of the cell center point. */ -void _vec2dToVec3(const Vec2d *v, int face, int res, int substrate, - Vec3d *v3d) { - // calculate (r, theta) in Vec2d - double r = _v2dMag(v); +void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { + // calculate (r, theta) in Vec2 + double r = _vec2Mag(v); if (r < EPSILON) { - *v3d = faceCenterPoint[face]; + *v3 = faceCenterPoint[face]; return; } @@ -507,20 +506,20 @@ void _vec2dToVec3(const Vec2d *v, int face, int res, int substrate, theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); // now find the point at (r,theta) from the face center - const Vec3d *center = &faceCenterPoint[face]; - Vec3d northPole = {0.0, 0.0, 1.0}; + const Vec3 *center = &faceCenterPoint[face]; + Vec3 northPole = {0.0, 0.0, 1.0}; // local north direction on tangent plane. // N.B. this will not work if the center is at a pole, but // icosahedron faces are not at the poles. double NdotC = vec3Dot(&northPole, center); - Vec3d northDir = {northPole.x - NdotC * center->x, - northPole.y - NdotC * center->y, - northPole.z - NdotC * center->z}; + Vec3 northDir = {northPole.x - NdotC * center->x, + northPole.y - NdotC * center->y, + northPole.z - NdotC * center->z}; vec3Normalize(&northDir); // local east direction on tangent plane - Vec3d eastDir; + Vec3 eastDir; vec3Cross(&northDir, center, &eastDir); // Rodrigues' rotation formula, simplified for orthogonal vectors @@ -528,17 +527,17 @@ void _vec2dToVec3(const Vec2d *v, int face, int res, int substrate, // sin(theta) where `center x northDir` is `eastDir` double cosTheta = cos(theta); double sinTheta = sin(theta); - Vec3d dir = {northDir.x * cosTheta + eastDir.x * sinTheta, - northDir.y * cosTheta + eastDir.y * sinTheta, - northDir.z * cosTheta + eastDir.z * sinTheta}; + Vec3 dir = {northDir.x * cosTheta + eastDir.x * sinTheta, + northDir.y * cosTheta + eastDir.y * sinTheta, + northDir.z * cosTheta + eastDir.z * sinTheta}; // slerp to get the new point double cos_r = cos(r); double sin_r = sin(r); - v3d->x = center->x * cos_r + dir.x * sin_r; - v3d->y = center->y * cos_r + dir.y * sin_r; - v3d->z = center->z * cos_r + dir.z * sin_r; - vec3Normalize(v3d); + v3->x = center->x * cos_r + dir.x * sin_r; + v3->y = center->y * cos_r + dir.y * sin_r; + v3->z = center->z * cos_r + dir.z * sin_r; + vec3Normalize(v3); } /** @@ -547,12 +546,12 @@ void _vec2dToVec3(const Vec2d *v, int face, int res, int substrate, * * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. - * @param v3d The 3D coordinates of the cell center point. + * @param v3 The 3D coordinates of the cell center point. */ -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3d) { - Vec2d v; - _ijkToVec2d(&h->coord, &v); - _vec2dToVec3(&v, h->face, res, 0, v3d); +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3) { + Vec2 v; + _ijkToVec2(&h->coord, &v); + _vec2ToVec3(&v, h->face, res, 0, v3); } /** @@ -593,12 +592,12 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // note that Class II pentagons have vertices on the edge, // not edge intersections if (isResolutionClassIII(res) && vert > start) { - // find Vec2d of the two vertexes on the last face + // find Vec2 of the two vertexes on the last face FaceIJK tmpFijk = fijk; - Vec2d orig2d0; - _ijkToVec2d(&lastFijk.coord, &orig2d0); + Vec2 orig2d0; + _ijkToVec2(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -616,17 +615,17 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2d orig2d1; - _ijkToVec2d(ijk, &orig2d1); + Vec2 orig2d1; + _ijkToVec2(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}; + Vec2 v0 = {3.0 * maxDim, 0.0}; + Vec2 v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; + Vec2 v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; - Vec2d *edge0; - Vec2d *edge1; + Vec2 *edge0; + Vec2 *edge1; switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { case IJ: edge0 = &v0; @@ -645,11 +644,11 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, } // find the intersection and add the lat/lng point to the result - Vec2d inter; - _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - Vec3d v3d; - _vec2dToVec3(&inter, tmpFijk.face, adjRes, 1, &v3d); - vec3ToLatLng(&v3d, &g->verts[g->numVerts]); + Vec2 inter; + _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); + Vec3 v3; + _vec2ToVec3(&inter, tmpFijk.face, adjRes, 1, &v3); + vec3ToLatLng(&v3, &g->verts[g->numVerts]); g->numVerts++; } @@ -657,11 +656,11 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - Vec2d vec; - _ijkToVec2d(&fijk.coord, &vec); - Vec3d v3d; - _vec2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); - vec3ToLatLng(&v3d, &g->verts[g->numVerts]); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + Vec3 v3; + _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); + vec3ToLatLng(&v3, &g->verts[g->numVerts]); g->numVerts++; } @@ -778,23 +777,23 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, */ if (isResolutionClassIII(res) && vert > start && fijk.face != lastFace && lastOverage != FACE_EDGE) { - // find Vec2d of the two vertexes on original face + // find Vec2 of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2d orig2d0; - _ijkToVec2d(&fijkVerts[lastV].coord, &orig2d0); + Vec2 orig2d0; + _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); - Vec2d orig2d1; - _ijkToVec2d(&fijkVerts[v].coord, &orig2d1); + Vec2 orig2d1; + _ijkToVec2(&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}; + Vec2 v0 = {3.0 * maxDim, 0.0}; + Vec2 v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; + Vec2 v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; int face2 = ((lastFace == centerIJK.face) ? fijk.face : lastFace); - Vec2d *edge0; - Vec2d *edge1; + Vec2 *edge0; + Vec2 *edge1; switch (adjacentFaceDir[centerIJK.face][face2]) { case IJ: edge0 = &v0; @@ -813,19 +812,19 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, } // find the intersection and add the lat/lng point to the result - Vec2d inter; - _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); + Vec2 inter; + _vec2Intersect(&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 = _v2dAlmostEquals(&orig2d0, &inter) || - _v2dAlmostEquals(&orig2d1, &inter); + bool isIntersectionAtVertex = _vec2AlmostEquals(&orig2d0, &inter) || + _vec2AlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { - Vec3d v3d; - _vec2dToVec3(&inter, centerIJK.face, adjRes, 1, &v3d); - vec3ToLatLng(&v3d, &g->verts[g->numVerts]); + Vec3 v3; + _vec2ToVec3(&inter, centerIJK.face, adjRes, 1, &v3); + vec3ToLatLng(&v3, &g->verts[g->numVerts]); g->numVerts++; } } @@ -834,11 +833,11 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - Vec2d vec; - _ijkToVec2d(&fijk.coord, &vec); - Vec3d v3d; - _vec2dToVec3(&vec, fijk.face, adjRes, 1, &v3d); - vec3ToLatLng(&v3d, &g->verts[g->numVerts]); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + Vec3 v3; + _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); + vec3ToLatLng(&v3, &g->verts[g->numVerts]); g->numVerts++; } @@ -1002,21 +1001,21 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { } /** - * Encodes a Vec3d coordinate to the corresponding icosahedral face and + * Encodes a Vec3 coordinate to the corresponding icosahedral face and * squared euclidean distance to that face center. * - * @param v3d The Vec3d coordinates to encode. + * @param v3 The Vec3 coordinates to encode. * @param face The icosahedral face containing the coordinates. * @param sqd The squared euclidean distance to its icosahedral face center. */ -static void _vec3dToClosestFace(const Vec3d *v3d, int *face, double *sqd) { +static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { // determine the icosahedron face *face = 0; // The distance between two farthest points is 2.0, therefore the square of // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(&faceCenterPoint[f], v3d); + double sqdT = vec3DistSq(&faceCenterPoint[f], v3); if (sqdT < *sqd) { *face = f; *sqd = sqdT; diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 8c2a9917bd..11d43fb801 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1044,7 +1044,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { return E_LATLNG_DOMAIN; } - Vec3d v; + Vec3 v; latLngToVec3(g, &v); return vec3ToCell(&v, res, out); } @@ -1060,7 +1060,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { * @param out The encoded H3Index. * @returns E_SUCCESS on success, another value otherwise */ -H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out) { +H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { if (res < 0 || res > MAX_H3_RES) { return E_RES_DOMAIN; } @@ -1069,7 +1069,7 @@ H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out) { } FaceIJK fijk; - _vec3dToFaceIjk(v, res, &fijk); + _vec3ToFaceIjk(v, res, &fijk); *out = _faceIjkToH3(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; @@ -1174,7 +1174,7 @@ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { * @param g The spherical coordinates of the H3 cell center. */ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { - Vec3d v; + Vec3 v; H3Error e = cellToVec3(h3, &v); if (e) { return e; @@ -1190,7 +1190,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { * @param v The 3D cartesian coordinates of the H3 cell center. * @return E_SUCCESS on success, or another H3Error code on failure. */ -H3Error cellToVec3(H3Index h3, Vec3d *v) { +H3Error cellToVec3(H3Index h3, Vec3 *v) { FaceIJK fijk; H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { diff --git a/src/h3lib/lib/vec2d.c b/src/h3lib/lib/vec2.c similarity index 86% rename from src/h3lib/lib/vec2d.c rename to src/h3lib/lib/vec2.c index 2b4a121bf2..113e9156da 100644 --- a/src/h3lib/lib/vec2d.c +++ b/src/h3lib/lib/vec2.c @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec2d.c +/** @file vec2.c * @brief 2D floating point vector functions. */ -#include "vec2d.h" +#include "vec2.h" #include #include @@ -28,7 +28,7 @@ * @param v The 2D cartesian vector. * @return The magnitude of the vector. */ -double _v2dMag(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } +double _vec2Mag(const Vec2 *v) { return sqrt(v->x * v->x + v->y * v->y); } /** * Finds the intersection between two lines. Assumes that the lines intersect @@ -39,9 +39,9 @@ double _v2dMag(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } * @param p3 The second endpoint of the second line. * @param inter The intersection point. */ -void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, - const Vec2d *p3, Vec2d *inter) { - Vec2d s1, s2; +void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, + const Vec2 *p3, Vec2 *inter) { + Vec2 s1, s2; s1.x = p1->x - p0->x; s1.y = p1->y - p0->y; s2.x = p3->x - p2->x; @@ -61,7 +61,7 @@ void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, * @param v2 Second vector to compare * @return Whether the vectors are almost equal */ -bool _v2dAlmostEquals(const Vec2d *v1, const Vec2d *v2) { +bool _vec2AlmostEquals(const Vec2 *v1, const Vec2 *v2) { return fabs(v1->x - v2->x) < FLT_EPSILON && fabs(v1->y - v2->y) < FLT_EPSILON; } diff --git a/src/h3lib/lib/vec3d.c b/src/h3lib/lib/vec3.c similarity index 76% rename from src/h3lib/lib/vec3d.c rename to src/h3lib/lib/vec3.c index 06328d14a2..797abdaf13 100644 --- a/src/h3lib/lib/vec3d.c +++ b/src/h3lib/lib/vec3.c @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec3d.c +/** @file vec3.c * @brief 3D floating point vector functions. */ -#include "vec3d.h" +#include "vec3.h" #include @@ -29,7 +29,7 @@ * @param geo The latitude and longitude of the point. * @param v The 3D coordinate of the point. */ -void _geoToVec3d(const LatLng *geo, Vec3d *v) { +void latLngToVec3(const LatLng *geo, Vec3 *v) { double r = cos(geo->lat); v->z = sin(geo->lat); @@ -37,17 +37,17 @@ void _geoToVec3d(const LatLng *geo, Vec3d *v) { v->y = sin(geo->lng) * r; } -double vec3Dot(const Vec3d *v1, const Vec3d *v2) { +double vec3Dot(const Vec3 *v1, const Vec3 *v2) { return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; } -void vec3Cross(const Vec3d *v1, const Vec3d *v2, Vec3d *out) { +void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { out->x = v1->y * v2->z - v1->z * v2->y; out->y = v1->z * v2->x - v1->x * v2->z; out->z = v1->x * v2->y - v1->y * v2->x; } -void vec3Normalize(Vec3d *v) { +void vec3Normalize(Vec3 *v) { double mag = vec3Mag(v); // Check for zero-length vector to avoid division by zero. // Using a small epsilon for robustness. @@ -59,20 +59,18 @@ void vec3Normalize(Vec3d *v) { } } -double vec3MagSq(const Vec3d *v) { return vec3Dot(v, v); } +double vec3MagSq(const Vec3 *v) { return vec3Dot(v, v); } -double vec3Mag(const Vec3d *v) { return sqrt(vec3Dot(v, v)); } +double vec3Mag(const Vec3 *v) { return sqrt(vec3Dot(v, v)); } -double vec3DistSq(const Vec3d *v1, const Vec3d *v2) { +double vec3DistSq(const Vec3 *v1, const Vec3 *v2) { double dx = v1->x - v2->x; double dy = v1->y - v2->y; double dz = v1->z - v2->z; return dx * dx + dy * dy + dz * dz; } -void latLngToVec3(const LatLng *geo, Vec3d *v) { _geoToVec3d(geo, v); } - -void vec3ToLatLng(const Vec3d *v, LatLng *geo) { +void vec3ToLatLng(const Vec3 *v, LatLng *geo) { geo->lat = asin(v->z); geo->lng = atan2(v->y, v->x); } From 4021953fee3952828eafffb5245d564836500477 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 23:20:03 -0700 Subject: [PATCH 09/91] normalize --- src/apps/testapps/testVec2Internal.c | 4 ++-- src/apps/testapps/testVec3.c | 8 +++---- src/h3lib/include/vec2.h | 2 +- src/h3lib/include/vec3.h | 4 ++-- src/h3lib/lib/faceijk.c | 2 +- src/h3lib/lib/vec2.c | 2 +- src/h3lib/lib/vec3.c | 36 +++++++++++++--------------- 7 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/apps/testapps/testVec2Internal.c b/src/apps/testapps/testVec2Internal.c index 20bc0b3d5f..5029557b25 100644 --- a/src/apps/testapps/testVec2Internal.c +++ b/src/apps/testapps/testVec2Internal.c @@ -22,10 +22,10 @@ #include "vec2.h" SUITE(Vec2Internal) { - TEST(_vec2Mag) { + TEST(_vec2Norm) { Vec2 v = {3.0, 4.0}; double expected = 5.0; - double mag = _vec2Mag(&v); + double mag = _vec2Norm(&v); t_assert(fabs(mag - expected) < DBL_EPSILON, "magnitude as expected"); } diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index ba915d2b34..594a67e541 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -46,12 +46,12 @@ SUITE(Vec3) { TEST(normalizeAndMagnitude) { Vec3 v = {.x = 3.0, .y = -4.0, .z = 12.0}; - double magSq = vec3MagSq(&v); + double magSq = vec3NormSq(&v); t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); - t_assert(fabs(vec3Mag(&v) - 13.0) < EPSILON, "magnitude matches"); + t_assert(fabs(vec3Norm(&v) - 13.0) < EPSILON, "magnitude matches"); vec3Normalize(&v); - t_assert(fabs(vec3Mag(&v) - 1.0) < 1e-12, "normalized vector is unit"); + t_assert(fabs(vec3Norm(&v) - 1.0) < 1e-12, "normalized vector is unit"); Vec3 zero = {.x = 0.0, .y = 0.0, .z = 0.0}; vec3Normalize(&zero); @@ -70,7 +70,7 @@ SUITE(Vec3) { LatLng geo = {.lat = 0.5, .lng = -1.3}; Vec3 v; latLngToVec3(&geo, &v); - t_assert(fabs(vec3Mag(&v) - 1.0) < 1e-12, + t_assert(fabs(vec3Norm(&v) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } diff --git a/src/h3lib/include/vec2.h b/src/h3lib/include/vec2.h index 5dde602a7b..6a0a18d737 100644 --- a/src/h3lib/include/vec2.h +++ b/src/h3lib/include/vec2.h @@ -36,7 +36,7 @@ typedef struct { // Internal functions -double _vec2Mag(const Vec2 *v); +double _vec2Norm(const Vec2 *v); void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, const Vec2 *p3, Vec2 *inter); bool _vec2AlmostEquals(const Vec2 *p0, const Vec2 *p1); diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index 1202da13a9..f682bc4c01 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -38,8 +38,8 @@ typedef struct { double vec3Dot(const Vec3 *v1, const Vec3 *v2); void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out); void vec3Normalize(Vec3 *v); -double vec3MagSq(const Vec3 *v); -double vec3Mag(const Vec3 *v); +double vec3NormSq(const Vec3 *v); +double vec3Norm(const Vec3 *v); double vec3DistSq(const Vec3 *v1, const Vec3 *v2); void latLngToVec3(const LatLng *geo, Vec3 *v); void vec3ToLatLng(const Vec3 *v, LatLng *geo); diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 9b40ddd76f..a4f6850239 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -474,7 +474,7 @@ void _vec3ToFaceIjk(const Vec3 *p, int res, FaceIJK *h) { */ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { // calculate (r, theta) in Vec2 - double r = _vec2Mag(v); + double r = _vec2Norm(v); if (r < EPSILON) { *v3 = faceCenterPoint[face]; diff --git a/src/h3lib/lib/vec2.c b/src/h3lib/lib/vec2.c index 113e9156da..c2137dc9d6 100644 --- a/src/h3lib/lib/vec2.c +++ b/src/h3lib/lib/vec2.c @@ -28,7 +28,7 @@ * @param v The 2D cartesian vector. * @return The magnitude of the vector. */ -double _vec2Mag(const Vec2 *v) { return sqrt(v->x * v->x + v->y * v->y); } +double _vec2Norm(const Vec2 *v) { return sqrt(v->x * v->x + v->y * v->y); } /** * Finds the intersection between two lines. Assumes that the lines intersect diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index 797abdaf13..c75a50ae67 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -37,8 +37,9 @@ void latLngToVec3(const LatLng *geo, Vec3 *v) { v->y = sin(geo->lng) * r; } -double vec3Dot(const Vec3 *v1, const Vec3 *v2) { - return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; +void vec3ToLatLng(const Vec3 *v, LatLng *geo) { + geo->lat = asin(v->z); + geo->lng = atan2(v->y, v->x); } void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { @@ -47,21 +48,23 @@ void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { out->z = v1->x * v2->y - v1->y * v2->x; } -void vec3Normalize(Vec3 *v) { - double mag = vec3Mag(v); - // Check for zero-length vector to avoid division by zero. - // Using a small epsilon for robustness. - if (mag > EPSILON) { - double invMag = 1.0 / mag; - v->x *= invMag; - v->y *= invMag; - v->z *= invMag; - } +double vec3Dot(const Vec3 *v1, const Vec3 *v2) { + return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; } -double vec3MagSq(const Vec3 *v) { return vec3Dot(v, v); } +double vec3NormSq(const Vec3 *v) { return vec3Dot(v, v); } -double vec3Mag(const Vec3 *v) { return sqrt(vec3Dot(v, v)); } +double vec3Norm(const Vec3 *v) { return sqrt(vec3NormSq(v)); } + +void vec3Normalize(Vec3 *v) { + double norm = vec3Norm(v); + if (norm == 0.0) return; + + double inv = 1.0 / norm; + v->x *= inv; + v->y *= inv; + v->z *= inv; +} double vec3DistSq(const Vec3 *v1, const Vec3 *v2) { double dx = v1->x - v2->x; @@ -69,8 +72,3 @@ double vec3DistSq(const Vec3 *v1, const Vec3 *v2) { double dz = v1->z - v2->z; return dx * dx + dy * dy + dz * dz; } - -void vec3ToLatLng(const Vec3 *v, LatLng *geo) { - geo->lat = asin(v->z); - geo->lng = atan2(v->y, v->x); -} From 2a55747a7ddbdb06333e64cfb919a66e52ebc010 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 30 Mar 2026 23:30:48 -0700 Subject: [PATCH 10/91] h3 -> cell; years --- src/apps/miscapps/generateFaceCenterPoint.c | 2 +- .../miscapps/generatePentagonDirectionFaces.c | 4 ++-- .../testapps/testCellToBoundaryEdgeCases.c | 2 +- src/apps/testapps/testH3IndexInternal.c | 20 +++++++++---------- src/apps/testapps/testLatLngInternal.c | 2 +- src/apps/testapps/testVec2Internal.c | 2 +- src/apps/testapps/testVec3.c | 2 +- src/apps/testapps/testVec3Internal.c | 2 +- src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 2 +- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/h3Index.h | 8 ++++---- src/h3lib/include/latLng.h | 2 +- src/h3lib/include/vec2.h | 2 +- src/h3lib/include/vec3.h | 2 +- src/h3lib/lib/coordijk.c | 2 +- src/h3lib/lib/directedEdge.c | 4 ++-- src/h3lib/lib/faceijk.c | 2 +- src/h3lib/lib/h3Index.c | 18 ++++++++--------- src/h3lib/lib/latLng.c | 2 +- src/h3lib/lib/localij.c | 4 ++-- src/h3lib/lib/vec2.c | 2 +- src/h3lib/lib/vec3.c | 2 +- src/h3lib/lib/vertex.c | 6 +++--- 24 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index e9f1daf027..a9666bea15 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -1,5 +1,5 @@ /* - * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2018, 2020-2021, 2026 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. diff --git a/src/apps/miscapps/generatePentagonDirectionFaces.c b/src/apps/miscapps/generatePentagonDirectionFaces.c index 468d238bb5..185182ac9a 100644 --- a/src/apps/miscapps/generatePentagonDirectionFaces.c +++ b/src/apps/miscapps/generatePentagonDirectionFaces.c @@ -1,5 +1,5 @@ /* - * Copyright 2020 Uber Technologies, Inc. + * Copyright 2020, 2026 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ static void generate(void) { int r = 0; H3Index neighbor; h3NeighborRotations(pentagon, dir, &r, &neighbor); - _h3ToFaceIjk(neighbor, &fijk); + _cellToFaceIjk(neighbor, &fijk); if (dir > J_AXES_DIGIT) printf(", "); printf("%d", fijk.face); diff --git a/src/apps/testapps/testCellToBoundaryEdgeCases.c b/src/apps/testapps/testCellToBoundaryEdgeCases.c index 3289bf7e1c..2cf62f4cbb 100644 --- a/src/apps/testapps/testCellToBoundaryEdgeCases.c +++ b/src/apps/testapps/testCellToBoundaryEdgeCases.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 Uber Technologies, Inc. + * Copyright 2017-2021, 2026 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. diff --git a/src/apps/testapps/testH3IndexInternal.c b/src/apps/testapps/testH3IndexInternal.c index c7ed6c5cbe..6703912a92 100644 --- a/src/apps/testapps/testH3IndexInternal.c +++ b/src/apps/testapps/testH3IndexInternal.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021, 2026 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. @@ -31,24 +31,24 @@ SUITE(h3IndexInternal) { TEST(faceIjkToH3ExtremeCoordinates) { FaceIJK fijk0I = {0, {3, 0, 0}}; - t_assert(_faceIjkToH3(&fijk0I, 0) == 0, "i out of bounds at res 0"); + t_assert(_faceIjkToCell(&fijk0I, 0) == 0, "i out of bounds at res 0"); FaceIJK fijk0J = {1, {0, 4, 0}}; - t_assert(_faceIjkToH3(&fijk0J, 0) == 0, "j out of bounds at res 0"); + t_assert(_faceIjkToCell(&fijk0J, 0) == 0, "j out of bounds at res 0"); FaceIJK fijk0K = {2, {2, 0, 5}}; - t_assert(_faceIjkToH3(&fijk0K, 0) == 0, "k out of bounds at res 0"); + t_assert(_faceIjkToCell(&fijk0K, 0) == 0, "k out of bounds at res 0"); FaceIJK fijk1I = {3, {6, 0, 0}}; - t_assert(_faceIjkToH3(&fijk1I, 1) == 0, "i out of bounds at res 1"); + t_assert(_faceIjkToCell(&fijk1I, 1) == 0, "i out of bounds at res 1"); FaceIJK fijk1J = {4, {0, 7, 1}}; - t_assert(_faceIjkToH3(&fijk1J, 1) == 0, "j out of bounds at res 1"); + t_assert(_faceIjkToCell(&fijk1J, 1) == 0, "j out of bounds at res 1"); FaceIJK fijk1K = {5, {2, 0, 8}}; - t_assert(_faceIjkToH3(&fijk1K, 1) == 0, "k out of bounds at res 1"); + t_assert(_faceIjkToCell(&fijk1K, 1) == 0, "k out of bounds at res 1"); FaceIJK fijk2I = {6, {18, 0, 0}}; - t_assert(_faceIjkToH3(&fijk2I, 2) == 0, "i out of bounds at res 2"); + t_assert(_faceIjkToCell(&fijk2I, 2) == 0, "i out of bounds at res 2"); FaceIJK fijk2J = {7, {0, 19, 1}}; - t_assert(_faceIjkToH3(&fijk2J, 2) == 0, "j out of bounds at res 2"); + t_assert(_faceIjkToCell(&fijk2J, 2) == 0, "j out of bounds at res 2"); FaceIJK fijk2K = {8, {2, 0, 20}}; - t_assert(_faceIjkToH3(&fijk2K, 2) == 0, "k out of bounds at res 2"); + t_assert(_faceIjkToCell(&fijk2K, 2) == 0, "k out of bounds at res 2"); } } diff --git a/src/apps/testapps/testLatLngInternal.c b/src/apps/testapps/testLatLngInternal.c index cdc714275c..747504087a 100644 --- a/src/apps/testapps/testLatLngInternal.c +++ b/src/apps/testapps/testLatLngInternal.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021 Uber Technologies, Inc. + * Copyright 2017-2021, 2026 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. diff --git a/src/apps/testapps/testVec2Internal.c b/src/apps/testapps/testVec2Internal.c index 5029557b25..01485102e7 100644 --- a/src/apps/testapps/testVec2Internal.c +++ b/src/apps/testapps/testVec2Internal.c @@ -1,5 +1,5 @@ /* - * Copyright 2018 Uber Technologies, Inc. + * Copyright 2018, 2026 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. diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index 594a67e541..4d92d21e3d 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -1,5 +1,5 @@ /* - * Copyright 2025 Uber Technologies, Inc. + * Copyright 2026 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. diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3Internal.c index 8b98737870..c61b3d101e 100644 --- a/src/apps/testapps/testVec3Internal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -1,5 +1,5 @@ /* - * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2018, 2020-2021, 2026 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. diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index d536facb1d..703888bfc8 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017, 2020 Uber Technologies, Inc. + * Copyright 2016-2017, 2020, 2026 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. diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 0beb53fb5c..a195284805 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2022 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2022, 2026 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. diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 78474f348b..57f4992503 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 Uber Technologies, Inc. + * Copyright 2016-2021, 2026 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. diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index ff015f9d0e..8faf30228b 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020 Uber Technologies, Inc. + * Copyright 2016-2018, 2020, 2026 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. @@ -169,9 +169,9 @@ int isResolutionClassIII(int r); // Internal functions -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); -H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk); -H3Index _faceIjkToH3(const FaceIJK *fijk, int res); +int _cellToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); +H3Error _cellToFaceIjk(H3Index h, FaceIJK *fijk); +H3Index _faceIjkToCell(const FaceIJK *fijk, int res); Direction _h3LeadingNonZeroDigit(H3Index h); H3Index _h3RotatePent60ccw(H3Index h); H3Index _h3RotatePent60cw(H3Index h); diff --git a/src/h3lib/include/latLng.h b/src/h3lib/include/latLng.h index 81268957de..bc11df5548 100644 --- a/src/h3lib/include/latLng.h +++ b/src/h3lib/include/latLng.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 Uber Technologies, Inc. + * Copyright 2016-2021, 2026 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. diff --git a/src/h3lib/include/vec2.h b/src/h3lib/include/vec2.h index 6a0a18d737..158b4931d8 100644 --- a/src/h3lib/include/vec2.h +++ b/src/h3lib/include/vec2.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2017, 2026 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. diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index f682bc4c01..3d86655f3d 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -1,5 +1,5 @@ /* - * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2018, 2020-2021, 2026 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. diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 34162440b6..1c6c52e375 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2023 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2023, 2026 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. diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c index 131a86160c..fadd3265d4 100644 --- a/src/h3lib/lib/directedEdge.c +++ b/src/h3lib/lib/directedEdge.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021, 2026 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. @@ -278,7 +278,7 @@ H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { // resulting edge boundary may have an additional distortion vertex if it // crosses an edge of the icosahedron. FaceIJK fijk; - H3Error fijkResult = _h3ToFaceIjk(origin, &fijk); + H3Error fijkResult = _cellToFaceIjk(origin, &fijk); if (NEVER(fijkResult)) { return fijkResult; } diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index a4f6850239..adc5572175 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 Uber Technologies, Inc. + * Copyright 2016-2023, 2026 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. diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 11d43fb801..43ff223f40 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021, 2024 Uber Technologies, Inc. + * Copyright 2016-2021, 2024, 2026 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. @@ -939,7 +939,7 @@ H3Index _h3Rotate60cw(H3Index h) { * @param res The cell resolution. * @return The encoded H3Index (or H3_NULL on failure). */ -H3Index _faceIjkToH3(const FaceIJK *fijk, int res) { +H3Index _faceIjkToCell(const FaceIJK *fijk, int res) { // initialize the index H3Index h = H3_INIT; H3_SET_MODE(h, H3_CELL_MODE); @@ -1070,7 +1070,7 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { FaceIJK fijk; _vec3ToFaceIjk(v, res, &fijk); - *out = _faceIjkToH3(&fijk, res); + *out = _faceIjkToCell(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; } else { @@ -1085,7 +1085,7 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { * and normalized base cell coordinates. * @return Returns 1 if the possibility of overage exists, otherwise 0. */ -int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { +int _cellToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { CoordIJK *ijk = &fijk->coord; int res = H3_GET_RESOLUTION(h); @@ -1116,7 +1116,7 @@ int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { * @param h The H3Index. * @param fijk The corresponding FaceIJK address. */ -H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { +H3Error _cellToFaceIjk(H3Index h, FaceIJK *fijk) { int baseCell = H3_GET_BASE_CELL(h); if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { // Base cells less than zero can not be represented in an index @@ -1132,7 +1132,7 @@ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { // start with the "home" face and ijk+ coordinates for the base cell of c *fijk = baseCellData[baseCell].homeFijk; - if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) + if (!_cellToFaceIjkWithInitializedFijk(h, fijk)) return E_SUCCESS; // no overage is possible; h lies on this face // if we're here we have the potential for an "overage"; i.e., it is @@ -1192,7 +1192,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { */ H3Error cellToVec3(H3Index h3, Vec3 *v) { FaceIJK fijk; - H3Error e = _h3ToFaceIjk(h3, &fijk); + H3Error e = _cellToFaceIjk(h3, &fijk); if (e) { return e; } @@ -1208,7 +1208,7 @@ H3Error cellToVec3(H3Index h3, Vec3 *v) { */ H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { FaceIJK fijk; - H3Error e = _h3ToFaceIjk(h3, &fijk); + H3Error e = _cellToFaceIjk(h3, &fijk); if (e) { return e; } @@ -1260,7 +1260,7 @@ H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out) { // convert to FaceIJK FaceIJK fijk; - H3Error err = _h3ToFaceIjk(h3, &fijk); + H3Error err = _cellToFaceIjk(h3, &fijk); if (err) { return err; } diff --git a/src/h3lib/lib/latLng.c b/src/h3lib/lib/latLng.c index c2bf19045c..53b024ae5a 100644 --- a/src/h3lib/lib/latLng.c +++ b/src/h3lib/lib/latLng.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 Uber Technologies, Inc. + * Copyright 2016-2023, 2026 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. diff --git a/src/h3lib/lib/localij.c b/src/h3lib/lib/localij.c index 489023f034..43b7a7e7e0 100644 --- a/src/h3lib/lib/localij.c +++ b/src/h3lib/lib/localij.c @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020 Uber Technologies, Inc. + * Copyright 2018-2020, 2026 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. @@ -188,7 +188,7 @@ H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out) { } } // Face is unused. This produces coordinates in base cell coordinate space. - _h3ToFaceIjkWithInitializedFijk(h3, &indexFijk); + _cellToFaceIjkWithInitializedFijk(h3, &indexFijk); if (dir != CENTER_DIGIT) { assert(baseCell != originBaseCell); diff --git a/src/h3lib/lib/vec2.c b/src/h3lib/lib/vec2.c index c2137dc9d6..173c01c0ec 100644 --- a/src/h3lib/lib/vec2.c +++ b/src/h3lib/lib/vec2.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Uber Technologies, Inc. + * Copyright 2016-2017, 2026 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. diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index c75a50ae67..0aa464c32a 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -1,5 +1,5 @@ /* - * Copyright 2018, 2020-2021 Uber Technologies, Inc. + * Copyright 2018, 2020-2021, 2026 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. diff --git a/src/h3lib/lib/vertex.c b/src/h3lib/lib/vertex.c index 5f3755a9c9..f433d4bf6e 100644 --- a/src/h3lib/lib/vertex.c +++ b/src/h3lib/lib/vertex.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 Uber Technologies, Inc. + * Copyright 2020-2021, 2026 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. @@ -53,7 +53,7 @@ static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = { static H3Error vertexRotations(H3Index cell, int *out) { // Get the face and other info for the origin FaceIJK fijk; - H3Error err = _h3ToFaceIjk(cell, &fijk); + H3Error err = _cellToFaceIjk(cell, &fijk); if (err) { return err; } @@ -329,7 +329,7 @@ H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { // Get the single vertex from the boundary CellBoundary gb; FaceIJK fijk; - H3Error fijkError = _h3ToFaceIjk(owner, &fijk); + H3Error fijkError = _cellToFaceIjk(owner, &fijk); if (fijkError) { return fijkError; } From 35b3302fff977fd312189d4a3d7456f7b6b14d38 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 01:18:43 -0700 Subject: [PATCH 11/91] bench --- bench.py | 141 +++++++++++++++++++++++++ src/apps/benchmarks/benchmarkCoreApi.c | 130 +++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 bench.py create mode 100644 src/apps/benchmarks/benchmarkCoreApi.c diff --git a/bench.py b/bench.py new file mode 100644 index 0000000000..049532ecf7 --- /dev/null +++ b/bench.py @@ -0,0 +1,141 @@ +# /// script +# requires-python = ">=3.10" +# /// + +""" +Run benchmarkCoreApi multiple times on two git refs and compare. + +Usage: edit BRANCH_A, BRANCH_B, and N_RUNS below, then run with: + uv run bench.py +""" + +import subprocess +import re +import os + +BRANCH_A = "master" +BRANCH_B = "vec3d-core" +N_RUNS = 10 +BUILD_DIR = "build" +BENCH_BIN = "bin/benchmarkCoreApi" + +ROOT = os.path.dirname(os.path.abspath(__file__)) + + +def run(cmd, **kwargs): + return subprocess.run(cmd, capture_output=True, text=True, + cwd=ROOT, **kwargs) + + +def build_bench(ref): + """Check out ref, build benchmarkCoreApi in release mode.""" + run(["git", "checkout", ref]) + # The benchmark file may not exist on the other branch, so copy it + run(["mkdir", "-p", f"{BUILD_DIR}"]) + r = subprocess.run( + f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", + shell=True, capture_output=True, text=True, cwd=ROOT + ) + if r.returncode != 0: + print(f"Build failed on {ref}:") + print(r.stderr[-500:] if len(r.stderr) > 500 else r.stderr) + return False + return True + + +def parse_output(text): + """Parse benchmark output lines into dict of {name: us_per_call}.""" + results = {} + for line in text.strip().split("\n"): + m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) + if m: + results[m.group(1)] = float(m.group(2)) + return results + + +def run_bench(n_runs): + """Run benchmark n_runs times, return dict of {name: min_us}.""" + all_results = {} + bin_path = os.path.join(ROOT, BUILD_DIR, BENCH_BIN) + for i in range(n_runs): + r = subprocess.run([bin_path], capture_output=True, text=True) + parsed = parse_output(r.stdout) + for name, us in parsed.items(): + if name not in all_results or us < all_results[name]: + all_results[name] = us + return all_results + + +def main(): + # Save current branch + r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + original_branch = r.stdout.strip() + + # Copy benchmark file so it exists on both branches + bench_src = os.path.join(ROOT, "src/apps/benchmarks/benchmarkCoreApi.c") + with open(bench_src) as f: + bench_content = f.read() + + # Also need the CMakeLists line - read current CMakeLists + cmake_path = os.path.join(ROOT, "CMakeLists.txt") + with open(cmake_path) as f: + cmake_content = f.read() + cmake_line = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" + + results = {} + for ref in [BRANCH_A, BRANCH_B]: + print(f"\n{'='*50}") + print(f"Benchmarking: {ref}") + print(f"{'='*50}") + + run(["git", "checkout", ref]) + + # Ensure benchmark file exists on this branch + with open(bench_src, "w") as f: + f.write(bench_content) + + # Ensure CMakeLists has the benchmark entry + with open(cmake_path) as f: + current_cmake = f.read() + if "benchmarkCoreApi" not in current_cmake: + current_cmake = current_cmake.replace( + "add_h3_benchmark(benchmarkH3Api", + cmake_line + "\n add_h3_benchmark(benchmarkH3Api" + ) + with open(cmake_path, "w") as f: + f.write(current_cmake) + + if not build_bench(ref): + print(f"Skipping {ref}") + continue + + print(f"Running {N_RUNS} iterations...") + results[ref] = run_bench(N_RUNS) + for name, us in results[ref].items(): + print(f" {name}: {us:.4f} us/call (min of {N_RUNS})") + + # Clean up uncommitted benchmark file on non-original branches + run(["git", "checkout", "--", "."]) + + # Restore original branch + run(["git", "checkout", original_branch]) + + # Print comparison + if len(results) == 2: + print(f"\n{'='*50}") + print(f"Comparison (min of {N_RUNS} runs)") + print(f"{'='*50}") + print(f"{'Function':<20} {BRANCH_A:>12} {BRANCH_B:>12} {'Change':>10}") + print(f"{'-'*20} {'-'*12} {'-'*12} {'-'*10}") + + for name in results[BRANCH_A]: + a = results[BRANCH_A][name] + b = results[BRANCH_B].get(name) + if b is not None: + pct = (b - a) / a * 100 + sign = "+" if pct > 0 else "" + print(f"{name:<20} {a:>10.4f}us {b:>10.4f}us {sign}{pct:>8.1f}%") + + +if __name__ == "__main__": + main() diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c new file mode 100644 index 0000000000..9b3f9eafe0 --- /dev/null +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -0,0 +1,130 @@ +/* + * Copyright 2026 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 benchmarkCoreApi.c + * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary with + * diverse inputs across faces and resolutions. + */ + +#include +#include + +#include "h3api.h" + +#define N_POINTS 20 +#define N_RESOLUTIONS 4 +#define ITERATIONS 50000 + +// Points spread across the globe (hitting different icosahedron faces) +static const LatLng points[N_POINTS] = { + {0.659966917655, -2.1364398519396}, // San Francisco area + {0.8527087756, -0.0405865662}, // London area + {0.6234025842, 2.0075945568}, // Tokyo area + {-0.5934119457, 2.5368879644}, // Sydney area + {0.4799655443, 0.6457718232}, // Cairo area + {-0.4014257280, -0.7610418886}, // São Paulo area + {0.9679776674, -1.7453292520}, // Alaska + {-1.2217304764, 0.0000000000}, // near south pole + {1.2217304764, 0.0000000000}, // near north pole + {0.0000000000, 0.0000000000}, // null island + {0.0000000000, 3.1415926536}, // antimeridian + {0.7853981634, 1.5707963268}, // 45N 90E + {-0.7853981634, -1.5707963268}, // 45S 90W + {0.3490658504, -1.2217304764}, // 20N 70W + {-0.1745329252, 0.5235987756}, // 10S 30E + {1.0471975512, -0.5235987756}, // 60N 30W + {-1.0471975512, 2.0943951024}, // 60S 120E + {0.2617993878, 1.8325957146}, // 15N 105E + {-0.8726646260, -1.0471975512}, // 50S 60W + {0.5235987756, -2.6179938780}, // 30N 150W +}; + +static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; + +static double elapsed_us(struct timespec *start, struct timespec *end) { + double sec = end->tv_sec - start->tv_sec; + double nsec = end->tv_nsec - start->tv_nsec; + return (sec * 1e9 + nsec) / 1e3; +} + +int main(void) { + H3Index cells[N_POINTS * N_RESOLUTIONS]; + int nCells = 0; + + // Pre-compute cells for inverse benchmarks + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); + nCells++; + } + } + + // Benchmark latLngToCell + { + struct timespec start, end; + H3Index h; + clock_gettime(CLOCK_MONOTONIC, &start); + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); + } + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + double us = elapsed_us(&start, &end); + double per_call = us / (ITERATIONS * N_POINTS * N_RESOLUTIONS); + printf("latLngToCell: %.4f us/call (%d calls)\n", + per_call, ITERATIONS * N_POINTS * N_RESOLUTIONS); + } + + // Benchmark cellToLatLng + { + struct timespec start, end; + LatLng out; + clock_gettime(CLOCK_MONOTONIC, &start); + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToLatLng)(cells[c], &out); + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + double us = elapsed_us(&start, &end); + double per_call = us / (ITERATIONS * nCells); + printf("cellToLatLng: %.4f us/call (%d calls)\n", + per_call, ITERATIONS * nCells); + } + + // Benchmark cellToBoundary + { + struct timespec start, end; + CellBoundary cb; + clock_gettime(CLOCK_MONOTONIC, &start); + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToBoundary)(cells[c], &cb); + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + double us = elapsed_us(&start, &end); + double per_call = us / (ITERATIONS * nCells); + printf("cellToBoundary: %.4f us/call (%d calls)\n", + per_call, ITERATIONS * nCells); + } + + return 0; +} From f7e9e443ece1719a744cc52f98910d815b3d6ec9 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 01:37:22 -0700 Subject: [PATCH 12/91] update bench --- bench.py | 143 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 81 insertions(+), 62 deletions(-) diff --git a/bench.py b/bench.py index 049532ecf7..4102e96181 100644 --- a/bench.py +++ b/bench.py @@ -5,6 +5,9 @@ """ Run benchmarkCoreApi multiple times on two git refs and compare. +Copies the benchmark C file and CMakeLists entry to a temp location +so they survive branch switches. Restores the original branch on exit. + Usage: edit BRANCH_A, BRANCH_B, and N_RUNS below, then run with: uv run bench.py """ @@ -12,6 +15,8 @@ import subprocess import re import os +import shutil +import tempfile BRANCH_A = "master" BRANCH_B = "vec3d-core" @@ -20,6 +25,8 @@ BENCH_BIN = "bin/benchmarkCoreApi" ROOT = os.path.dirname(os.path.abspath(__file__)) +BENCH_SRC_REL = "src/apps/benchmarks/benchmarkCoreApi.c" +CMAKE_LINE = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" def run(cmd, **kwargs): @@ -27,24 +34,7 @@ def run(cmd, **kwargs): cwd=ROOT, **kwargs) -def build_bench(ref): - """Check out ref, build benchmarkCoreApi in release mode.""" - run(["git", "checkout", ref]) - # The benchmark file may not exist on the other branch, so copy it - run(["mkdir", "-p", f"{BUILD_DIR}"]) - r = subprocess.run( - f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", - shell=True, capture_output=True, text=True, cwd=ROOT - ) - if r.returncode != 0: - print(f"Build failed on {ref}:") - print(r.stderr[-500:] if len(r.stderr) > 500 else r.stderr) - return False - return True - - def parse_output(text): - """Parse benchmark output lines into dict of {name: us_per_call}.""" results = {} for line in text.strip().split("\n"): m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) @@ -54,7 +44,6 @@ def parse_output(text): def run_bench(n_runs): - """Run benchmark n_runs times, return dict of {name: min_us}.""" all_results = {} bin_path = os.path.join(ROOT, BUILD_DIR, BENCH_BIN) for i in range(n_runs): @@ -66,59 +55,89 @@ def run_bench(n_runs): return all_results +def setup_bench_on_branch(bench_content): + """Ensure benchmark C file and CMakeLists entry exist on current checkout.""" + bench_path = os.path.join(ROOT, BENCH_SRC_REL) + cmake_path = os.path.join(ROOT, "CMakeLists.txt") + + os.makedirs(os.path.dirname(bench_path), exist_ok=True) + with open(bench_path, "w") as f: + f.write(bench_content) + + with open(cmake_path) as f: + cmake = f.read() + if "benchmarkCoreApi" not in cmake: + cmake = cmake.replace( + "add_h3_benchmark(benchmarkH3Api", + CMAKE_LINE + "\n add_h3_benchmark(benchmarkH3Api" + ) + with open(cmake_path, "w") as f: + f.write(cmake) + + +def cleanup_branch(): + """Restore tracked files and remove untracked benchmark file.""" + bench_path = os.path.join(ROOT, BENCH_SRC_REL) + run(["git", "checkout", "--", "."]) + # Remove benchmark file if it's untracked (doesn't exist on this branch) + r = run(["git", "ls-files", BENCH_SRC_REL]) + if not r.stdout.strip() and os.path.exists(bench_path): + os.remove(bench_path) + + def main(): # Save current branch r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) original_branch = r.stdout.strip() - # Copy benchmark file so it exists on both branches - bench_src = os.path.join(ROOT, "src/apps/benchmarks/benchmarkCoreApi.c") - with open(bench_src) as f: + # Save benchmark C file content before any branch switches + bench_path = os.path.join(ROOT, BENCH_SRC_REL) + if not os.path.exists(bench_path): + print(f"Error: {BENCH_SRC_REL} not found. Run from the branch that has it.") + return + with open(bench_path) as f: bench_content = f.read() - # Also need the CMakeLists line - read current CMakeLists - cmake_path = os.path.join(ROOT, "CMakeLists.txt") - with open(cmake_path) as f: - cmake_content = f.read() - cmake_line = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" - results = {} - for ref in [BRANCH_A, BRANCH_B]: - print(f"\n{'='*50}") - print(f"Benchmarking: {ref}") - print(f"{'='*50}") - - run(["git", "checkout", ref]) - - # Ensure benchmark file exists on this branch - with open(bench_src, "w") as f: - f.write(bench_content) - - # Ensure CMakeLists has the benchmark entry - with open(cmake_path) as f: - current_cmake = f.read() - if "benchmarkCoreApi" not in current_cmake: - current_cmake = current_cmake.replace( - "add_h3_benchmark(benchmarkH3Api", - cmake_line + "\n add_h3_benchmark(benchmarkH3Api" + try: + for ref in [BRANCH_A, BRANCH_B]: + print(f"\n{'='*50}") + print(f"Benchmarking: {ref}") + print(f"{'='*50}") + + run(["git", "checkout", ref]) + + # Verify branch + r = run(["git", "rev-parse", "--short", "HEAD"]) + sha = r.stdout.strip() + r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + branch = r.stdout.strip() + print(f" branch: {branch} commit: {sha}") + + setup_bench_on_branch(bench_content) + + # Build + run(["mkdir", "-p", BUILD_DIR]) + r = subprocess.run( + f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", + shell=True, capture_output=True, text=True, cwd=ROOT ) - with open(cmake_path, "w") as f: - f.write(current_cmake) - - if not build_bench(ref): - print(f"Skipping {ref}") - continue - - print(f"Running {N_RUNS} iterations...") - results[ref] = run_bench(N_RUNS) - for name, us in results[ref].items(): - print(f" {name}: {us:.4f} us/call (min of {N_RUNS})") - - # Clean up uncommitted benchmark file on non-original branches - run(["git", "checkout", "--", "."]) - - # Restore original branch - run(["git", "checkout", original_branch]) + if r.returncode != 0: + print(f"Build failed on {ref}:") + print(r.stderr[-500:]) + continue + + print(f"Running {N_RUNS} iterations...") + results[ref] = run_bench(N_RUNS) + for name, us in results[ref].items(): + print(f" {name}: {us:.4f} us/call (min of {N_RUNS})") + + cleanup_branch() + finally: + # Always restore original branch + cleanup_branch() + run(["git", "checkout", original_branch]) + print(f"\nRestored branch: {original_branch}") # Print comparison if len(results) == 2: From 2f5a9f468e362cef0c4ac82da78dda53f0904887 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 02:19:45 -0700 Subject: [PATCH 13/91] correctness --- src/apps/benchmarks/dumpCoreApi.c | 86 ++++++++++ stress.py | 273 ++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 src/apps/benchmarks/dumpCoreApi.c create mode 100644 stress.py diff --git a/src/apps/benchmarks/dumpCoreApi.c b/src/apps/benchmarks/dumpCoreApi.c new file mode 100644 index 0000000000..2f68c4fa7d --- /dev/null +++ b/src/apps/benchmarks/dumpCoreApi.c @@ -0,0 +1,86 @@ +/* + * Copyright 2026 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 dumpCoreApi.c + * @brief Dump latLngToCell, cellToLatLng, cellToBoundary results + * for cross-branch correctness comparison. + */ + +#include +#include +#include +#include + +#include "h3api.h" +#include "utility.h" + +#define N_RANDOM 10000 +#define SEED 12345 +#define MAX_EXHAUST_RES 4 + +static const int testResolutions[] = {0, 1, 2, 3, 4, 7, 10, 15}; +#define N_RES (sizeof(testResolutions) / sizeof(testResolutions[0])) + +static double rand01(void) { return (double)rand() / (double)RAND_MAX; } + +static void dumpCellToLatLng(H3Index h) { + LatLng g; + H3_EXPORT(cellToLatLng)(h, &g); + printf("%" PRIx64 " %.17g %.17g\n", h, g.lat, g.lng); +} + +static void dumpCellToBoundary(H3Index h) { + CellBoundary cb; + H3_EXPORT(cellToBoundary)(h, &cb); + printf("%" PRIx64 " %d", h, cb.numVerts); + for (int i = 0; i < cb.numVerts; i++) { + printf(" %.17g %.17g", cb.verts[i].lat, cb.verts[i].lng); + } + printf("\n"); +} + +int main(void) { + // Section 1: latLngToCell with random points + printf("#section latLngToCell\n"); + srand(SEED); + for (int i = 0; i < N_RANDOM; i++) { + // Uniform distribution on the sphere + double lat = asin(2.0 * rand01() - 1.0); + double lng = (2.0 * rand01() - 1.0) * M_PI; + LatLng g = {lat, lng}; + for (int r = 0; r < (int)N_RES; r++) { + H3Index h; + H3_EXPORT(latLngToCell)(&g, testResolutions[r], &h); + printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, + testResolutions[r], h); + } + } + + // Section 2: cellToLatLng exhaustive + printf("#section cellToLatLng\n"); + for (int res = 0; res <= MAX_EXHAUST_RES; res++) { + iterateAllIndexesAtRes(res, dumpCellToLatLng); + } + + // Section 3: cellToBoundary exhaustive + printf("#section cellToBoundary\n"); + for (int res = 0; res <= MAX_EXHAUST_RES; res++) { + iterateAllIndexesAtRes(res, dumpCellToBoundary); + } + + return 0; +} diff --git a/stress.py b/stress.py new file mode 100644 index 0000000000..68b2f22650 --- /dev/null +++ b/stress.py @@ -0,0 +1,273 @@ +# /// script +# requires-python = ">=3.10" +# /// + +""" +Run dumpCoreApi on two git refs and compare results. + +Usage: edit BRANCH_A, BRANCH_B below, then run with: + uv run stress.py +""" + +import subprocess +import os +import math + +BRANCH_A = "master" +BRANCH_B = "vec3d-core" +BUILD_DIR = "build" +DUMP_BIN = "bin/dumpCoreApi" + +ROOT = os.path.dirname(os.path.abspath(__file__)) +DUMP_SRC_REL = "src/apps/benchmarks/dumpCoreApi.c" +CMAKE_LINE = " add_h3_benchmark(dumpCoreApi src/apps/benchmarks/dumpCoreApi.c)" + + +def run(cmd): + return subprocess.run(cmd, capture_output=True, text=True, cwd=ROOT) + + +def prepare_branch(dump_content): + """Write dump file and CMakeLists entry onto current checkout.""" + dump_path = os.path.join(ROOT, DUMP_SRC_REL) + cmake_path = os.path.join(ROOT, "CMakeLists.txt") + + os.makedirs(os.path.dirname(dump_path), exist_ok=True) + with open(dump_path, "w") as f: + f.write(dump_content) + + with open(cmake_path) as f: + cmake = f.read() + if "dumpCoreApi" not in cmake: + cmake = cmake.replace( + "add_h3_benchmark(benchmarkH3Api", + CMAKE_LINE + "\n add_h3_benchmark(benchmarkH3Api" + ) + with open(cmake_path, "w") as f: + f.write(cmake) + + +def restore_branch(): + """Undo modifications to tracked files, remove untracked dump file.""" + run(["git", "checkout", "--", "."]) + dump_path = os.path.join(ROOT, DUMP_SRC_REL) + # Remove if untracked on this branch + r = run(["git", "ls-files", DUMP_SRC_REL]) + if not r.stdout.strip() and os.path.exists(dump_path): + os.remove(dump_path) + + +def switch_to(ref): + """Clean up current branch and switch to ref.""" + restore_branch() + r = run(["git", "checkout", ref]) + if r.returncode != 0: + print(f" checkout failed: {r.stderr.strip()}") + return False + return True + + +def get_branch_info(): + r1 = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + r2 = run(["git", "rev-parse", "--short", "HEAD"]) + return r1.stdout.strip(), r2.stdout.strip() + + +def build_and_run(ref, dump_content): + print(f"\n{'='*50}") + print(f"Running: {ref}") + print(f"{'='*50}") + + if not switch_to(ref): + return None + + branch, sha = get_branch_info() + print(f" branch: {branch} commit: {sha}") + + prepare_branch(dump_content) + + r = subprocess.run( + f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make dumpCoreApi", + shell=True, capture_output=True, text=True, cwd=ROOT + ) + if r.returncode != 0: + print(f"Build failed: {r.stderr[-300:]}") + return None + + bin_path = os.path.join(ROOT, BUILD_DIR, DUMP_BIN) + r = subprocess.run([bin_path], capture_output=True, text=True) + print(f" {len(r.stdout.splitlines())} lines of output") + return r.stdout + + +def parse_sections(text): + sections = {} + current = None + for line in text.splitlines(): + if line.startswith("#section "): + current = line.split(" ", 1)[1] + sections[current] = [] + elif current is not None: + sections[current].append(line) + return sections + + +def compare_latLngToCell(lines_a, lines_b): + print(f"\n--- latLngToCell ---") + if len(lines_a) != len(lines_b): + print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") + return + + mismatches = 0 + for la, lb in zip(lines_a, lines_b): + pa = la.split() + pb = lb.split() + if pa[3] != pb[3]: + mismatches += 1 + if mismatches <= 20: + print(f" MISMATCH: lat={pa[0]} lng={pa[1]} res={pa[2]}") + print(f" {BRANCH_A}: {pa[3]}") + print(f" {BRANCH_B}: {pb[3]}") + + print(f" Total: {len(lines_a)}") + print(f" Mismatches: {mismatches}") + if mismatches > 20: + print(f" (showing first 20 of {mismatches})") + + +def compare_cellToLatLng(lines_a, lines_b): + print(f"\n--- cellToLatLng ---") + if len(lines_a) != len(lines_b): + print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") + return + + max_lat_diff = 0.0 + max_lng_diff = 0.0 + max_lat_cell = "" + max_lng_cell = "" + n_exact = 0 + + for la, lb in zip(lines_a, lines_b): + pa = la.split() + pb = lb.split() + lat_a, lng_a = float(pa[1]), float(pa[2]) + lat_b, lng_b = float(pb[1]), float(pb[2]) + + dlat = abs(lat_a - lat_b) + dlng = abs(lng_a - lng_b) + if dlng > math.pi: + dlng = 2 * math.pi - dlng + + if dlat == 0.0 and dlng == 0.0: + n_exact += 1 + if dlat > max_lat_diff: + max_lat_diff = dlat + max_lat_cell = pa[0] + if dlng > max_lng_diff: + max_lng_diff = dlng + max_lng_cell = pa[0] + + total = len(lines_a) + print(f" Total cells: {total}") + print(f" Exact matches: {n_exact} ({100*n_exact/total:.1f}%)") + print(f" Max lat diff: {max_lat_diff:.3e} rad (cell {max_lat_cell})") + print(f" Max lng diff: {max_lng_diff:.3e} rad (cell {max_lng_cell})") + if max_lat_diff > 0: + print(f" Max lat diff: ~{max_lat_diff * 6.371e6:.6f} meters") + if max_lng_diff > 0: + print(f" Max lng diff: ~{max_lng_diff * 6.371e6:.6f} meters") + + +def compare_cellToBoundary(lines_a, lines_b): + print(f"\n--- cellToBoundary ---") + if len(lines_a) != len(lines_b): + print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") + return + + max_vert_diff = 0.0 + max_vert_cell = "" + max_vert_idx = -1 + vert_count_mismatches = 0 + n_exact = 0 + + for la, lb in zip(lines_a, lines_b): + pa = la.split() + pb = lb.split() + nverts_a, nverts_b = int(pa[1]), int(pb[1]) + + if nverts_a != nverts_b: + vert_count_mismatches += 1 + continue + + cell_exact = True + for v in range(nverts_a): + lat_a = float(pa[2 + v * 2]) + lng_a = float(pa[3 + v * 2]) + lat_b = float(pb[2 + v * 2]) + lng_b = float(pb[3 + v * 2]) + + dlat = abs(lat_a - lat_b) + dlng = abs(lng_a - lng_b) + if dlng > math.pi: + dlng = 2 * math.pi - dlng + + diff = max(dlat, dlng) + if diff > 0: + cell_exact = False + if diff > max_vert_diff: + max_vert_diff = diff + max_vert_cell = pa[0] + max_vert_idx = v + + if cell_exact: + n_exact += 1 + + total = len(lines_a) + print(f" Total cells: {total}") + print(f" Exact matches: {n_exact} ({100*n_exact/total:.1f}%)") + print(f" Vertex count mismatches: {vert_count_mismatches}") + print(f" Max vertex diff: {max_vert_diff:.3e} rad (cell {max_vert_cell}, vertex {max_vert_idx})") + if max_vert_diff > 0: + print(f" Max vertex diff: ~{max_vert_diff * 6.371e6:.6f} meters") + + +def main(): + r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + original_branch = r.stdout.strip() + + # Read dump file content before any branch switching + dump_path = os.path.join(ROOT, DUMP_SRC_REL) + if not os.path.exists(dump_path): + print(f"Error: {DUMP_SRC_REL} not found.") + return + with open(dump_path) as f: + dump_content = f.read() + + outputs = {} + try: + for ref in [BRANCH_A, BRANCH_B]: + output = build_and_run(ref, dump_content) + if output is None: + return + outputs[ref] = output + finally: + switch_to(original_branch) + print(f"\nRestored branch: {original_branch}") + + if len(outputs) != 2: + return + + sec_a = parse_sections(outputs[BRANCH_A]) + sec_b = parse_sections(outputs[BRANCH_B]) + + print(f"\n{'='*50}") + print(f"Comparison: {BRANCH_A} vs {BRANCH_B}") + print(f"{'='*50}") + + compare_latLngToCell(sec_a["latLngToCell"], sec_b["latLngToCell"]) + compare_cellToLatLng(sec_a["cellToLatLng"], sec_b["cellToLatLng"]) + compare_cellToBoundary(sec_a["cellToBoundary"], sec_b["cellToBoundary"]) + + +if __name__ == "__main__": + main() From b310c515987deaf59d8f064ab07f27608d3801d8 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 02:25:08 -0700 Subject: [PATCH 14/91] test vertices and along edges --- CMakeLists.txt | 2 ++ src/apps/benchmarks/dumpCoreApi.c | 55 +++++++++++++++++++++++++------ stress.py | 6 ++-- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index adbb8e56f2..29749d7fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -665,6 +665,8 @@ if(BUILD_BENCHMARKS) endmacro() add_h3_benchmark(benchmarkH3Api src/apps/benchmarks/benchmarkH3Api.c) + add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c) + add_h3_benchmark(dumpCoreApi src/apps/benchmarks/dumpCoreApi.c) add_h3_benchmark(benchmarkGridDiskCells src/apps/benchmarks/benchmarkGridDiskCells.c) add_h3_benchmark(benchmarkGridPathCells diff --git a/src/apps/benchmarks/dumpCoreApi.c b/src/apps/benchmarks/dumpCoreApi.c index 2f68c4fa7d..80c8017862 100644 --- a/src/apps/benchmarks/dumpCoreApi.c +++ b/src/apps/benchmarks/dumpCoreApi.c @@ -31,12 +31,48 @@ #define N_RANDOM 10000 #define SEED 12345 #define MAX_EXHAUST_RES 4 +#define VERTEX_TEST_RES 3 static const int testResolutions[] = {0, 1, 2, 3, 4, 7, 10, 15}; #define N_RES (sizeof(testResolutions) / sizeof(testResolutions[0])) static double rand01(void) { return (double)rand() / (double)RAND_MAX; } +static void dumpLatLngToCell(double lat, double lng) { + LatLng g = {lat, lng}; + for (int r = 0; r < (int)N_RES; r++) { + H3Index h; + H3_EXPORT(latLngToCell)(&g, testResolutions[r], &h); + printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, + testResolutions[r], h); + } +} + +/** Spherical midpoint of two LatLng points. */ +static void midpoint(const LatLng *a, const LatLng *b, LatLng *out) { + double c1 = cos(a->lat), c2 = cos(b->lat); + double x = c1 * cos(a->lng) + c2 * cos(b->lng); + double y = c1 * sin(a->lng) + c2 * sin(b->lng); + double z = sin(a->lat) + sin(b->lat); + double norm = sqrt(x * x + y * y + z * z); + out->lat = asin(z / norm); + out->lng = atan2(y, x); +} + +static void dumpVerticesAndEdges(H3Index h) { + CellBoundary cb; + H3_EXPORT(cellToBoundary)(h, &cb); + for (int i = 0; i < cb.numVerts; i++) { + // Vertex point + dumpLatLngToCell(cb.verts[i].lat, cb.verts[i].lng); + // Edge midpoint (between this vertex and the next) + int j = (i + 1) % cb.numVerts; + LatLng mid; + midpoint(&cb.verts[i], &cb.verts[j], &mid); + dumpLatLngToCell(mid.lat, mid.lng); + } +} + static void dumpCellToLatLng(H3Index h) { LatLng g; H3_EXPORT(cellToLatLng)(h, &g); @@ -58,25 +94,24 @@ int main(void) { printf("#section latLngToCell\n"); srand(SEED); for (int i = 0; i < N_RANDOM; i++) { - // Uniform distribution on the sphere double lat = asin(2.0 * rand01() - 1.0); double lng = (2.0 * rand01() - 1.0) * M_PI; - LatLng g = {lat, lng}; - for (int r = 0; r < (int)N_RES; r++) { - H3Index h; - H3_EXPORT(latLngToCell)(&g, testResolutions[r], &h); - printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, - testResolutions[r], h); - } + dumpLatLngToCell(lat, lng); + } + + // Section 2: latLngToCell at cell vertices and edge midpoints + printf("#section latLngToCellVertices\n"); + for (int res = 0; res <= VERTEX_TEST_RES; res++) { + iterateAllIndexesAtRes(res, dumpVerticesAndEdges); } - // Section 2: cellToLatLng exhaustive + // Section 3: cellToLatLng exhaustive printf("#section cellToLatLng\n"); for (int res = 0; res <= MAX_EXHAUST_RES; res++) { iterateAllIndexesAtRes(res, dumpCellToLatLng); } - // Section 3: cellToBoundary exhaustive + // Section 4: cellToBoundary exhaustive printf("#section cellToBoundary\n"); for (int res = 0; res <= MAX_EXHAUST_RES; res++) { iterateAllIndexesAtRes(res, dumpCellToBoundary); diff --git a/stress.py b/stress.py index 68b2f22650..c9290a7b92 100644 --- a/stress.py +++ b/stress.py @@ -112,8 +112,8 @@ def parse_sections(text): return sections -def compare_latLngToCell(lines_a, lines_b): - print(f"\n--- latLngToCell ---") +def compare_latLngToCell(lines_a, lines_b, label="latLngToCell"): + print(f"\n--- {label} ---") if len(lines_a) != len(lines_b): print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") return @@ -265,6 +265,8 @@ def main(): print(f"{'='*50}") compare_latLngToCell(sec_a["latLngToCell"], sec_b["latLngToCell"]) + compare_latLngToCell(sec_a["latLngToCellVertices"], sec_b["latLngToCellVertices"], + label="latLngToCell (vertices & edges)") compare_cellToLatLng(sec_a["cellToLatLng"], sec_b["cellToLatLng"]) compare_cellToBoundary(sec_a["cellToBoundary"], sec_b["cellToBoundary"]) From f1f193882152cf45caf5beb91fd6a654682c4f8a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:02:36 -0700 Subject: [PATCH 15/91] _vec3TangentBasis helper --- src/h3lib/lib/faceijk.c | 70 ++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index adc5572175..f51c045607 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -361,9 +361,30 @@ static const int unitScaleByCIIres[] = { 5764801 // res 16 }; -// Private function declaration +// Private function declarations static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); +/** + * Compute the local north and east directions on the tangent plane + * at a point on the unit sphere. + * + * Will not work if p is at a pole, but icosahedron face centers + * are never at the poles. + * + * @param p Unit vector on the sphere. + * @param north Output: local north direction on tangent plane. + * @param east Output: local east direction on tangent plane. + */ +static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { + Vec3 northPole = {0.0, 0.0, 1.0}; + double NdotP = vec3Dot(&northPole, p); + north->x = northPole.x - NdotP * p->x; + north->y = northPole.y - NdotP * p->y; + north->z = northPole.z - NdotP * p->z; + vec3Normalize(north); + vec3Cross(north, p, east); +} + /** * Calculates the azimuth from p1 to p2. * @param p1 The first vector. @@ -371,31 +392,20 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); * @return The azimuth in radians. */ static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { - Vec3 northPole = {0.0, 0.0, 1.0}; - - // local north direction on tangent plane. - double NdotC = vec3Dot(&northPole, p1); - Vec3 northDir = {northPole.x - NdotC * p1->x, northPole.y - NdotC * p1->y, - northPole.z - NdotC * p1->z}; - vec3Normalize(&northDir); + Vec3 northDir, eastDir; + _vec3TangentBasis(p1, &northDir, &eastDir); - // local east direction on tangent plane - Vec3 eastDir; - vec3Cross(&northDir, p1, &eastDir); - - // vector from p1 to p2 on tangent plane - Vec3 p2_on_tangent; + // project p2 onto tangent plane at p1 double p2dotp1 = vec3Dot(p2, p1); - p2_on_tangent.x = p2->x - p2dotp1 * p1->x; - p2_on_tangent.y = p2->y - p2dotp1 * p1->y; - p2_on_tangent.z = p2->z - p2dotp1 * p1->z; + Vec3 p2_on_tangent = { + p2->x - p2dotp1 * p1->x, + p2->y - p2dotp1 * p1->y, + p2->z - p2dotp1 * p1->z, + }; vec3Normalize(&p2_on_tangent); - // azimuth is angle between north direction and p2_on_tangent - double azimuth = atan2(vec3Dot(&p2_on_tangent, &eastDir), - vec3Dot(&p2_on_tangent, &northDir)); - - return azimuth; + return atan2(vec3Dot(&p2_on_tangent, &eastDir), + vec3Dot(&p2_on_tangent, &northDir)); } /** @@ -507,20 +517,8 @@ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { // now find the point at (r,theta) from the face center const Vec3 *center = &faceCenterPoint[face]; - Vec3 northPole = {0.0, 0.0, 1.0}; - - // local north direction on tangent plane. - // N.B. this will not work if the center is at a pole, but - // icosahedron faces are not at the poles. - double NdotC = vec3Dot(&northPole, center); - Vec3 northDir = {northPole.x - NdotC * center->x, - northPole.y - NdotC * center->y, - northPole.z - NdotC * center->z}; - vec3Normalize(&northDir); - - // local east direction on tangent plane - Vec3 eastDir; - vec3Cross(&northDir, center, &eastDir); + Vec3 northDir, eastDir; + _vec3TangentBasis(center, &northDir, &eastDir); // Rodrigues' rotation formula, simplified for orthogonal vectors // Direction vector D = northDir * cos(theta) + (center x northDir) * From e2d586e498eb1ea287e0ff5463b4eba0da177145 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:11:23 -0700 Subject: [PATCH 16/91] vec3LinComb helper --- src/h3lib/include/vec3.h | 1 + src/h3lib/lib/faceijk.c | 25 +++++-------------------- src/h3lib/lib/vec3.c | 16 +++++++++++----- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index 3d86655f3d..9cd6bccfbf 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -35,6 +35,7 @@ typedef struct { double z; ///< z component (towards north pole) } Vec3; +Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b); double vec3Dot(const Vec3 *v1, const Vec3 *v2); void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out); void vec3Normalize(Vec3 *v); diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index f51c045607..9cfdd5288d 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -378,9 +378,7 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { Vec3 northPole = {0.0, 0.0, 1.0}; double NdotP = vec3Dot(&northPole, p); - north->x = northPole.x - NdotP * p->x; - north->y = northPole.y - NdotP * p->y; - north->z = northPole.z - NdotP * p->z; + *north = vec3LinComb(1.0, &northPole, -NdotP, p); vec3Normalize(north); vec3Cross(north, p, east); } @@ -397,11 +395,7 @@ static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { // project p2 onto tangent plane at p1 double p2dotp1 = vec3Dot(p2, p1); - Vec3 p2_on_tangent = { - p2->x - p2dotp1 * p1->x, - p2->y - p2dotp1 * p1->y, - p2->z - p2dotp1 * p1->z, - }; + Vec3 p2_on_tangent = vec3LinComb(1.0, p2, -p2dotp1, p1); vec3Normalize(&p2_on_tangent); return atan2(vec3Dot(&p2_on_tangent, &eastDir), @@ -523,18 +517,9 @@ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { // Rodrigues' rotation formula, simplified for orthogonal vectors // Direction vector D = northDir * cos(theta) + (center x northDir) * // sin(theta) where `center x northDir` is `eastDir` - double cosTheta = cos(theta); - double sinTheta = sin(theta); - Vec3 dir = {northDir.x * cosTheta + eastDir.x * sinTheta, - northDir.y * cosTheta + eastDir.y * sinTheta, - northDir.z * cosTheta + eastDir.z * sinTheta}; - - // slerp to get the new point - double cos_r = cos(r); - double sin_r = sin(r); - v3->x = center->x * cos_r + dir.x * sin_r; - v3->y = center->y * cos_r + dir.y * sin_r; - v3->z = center->z * cos_r + dir.z * sin_r; + Vec3 dir = vec3LinComb(cos(theta), &northDir, sin(theta), &eastDir); + + *v3 = vec3LinComb(cos(r), center, sin(r), &dir); vec3Normalize(v3); } diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index 0aa464c32a..3dae438f24 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -42,6 +42,14 @@ void vec3ToLatLng(const Vec3 *v, LatLng *geo) { geo->lng = atan2(v->y, v->x); } +Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b) { + return (Vec3){ + s1 * a->x + s2 * b->x, + s1 * a->y + s2 * b->y, + s1 * a->z + s2 * b->z, + }; +} + void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { out->x = v1->y * v2->z - v1->z * v2->y; out->y = v1->z * v2->x - v1->x * v2->z; @@ -49,7 +57,7 @@ void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { } double vec3Dot(const Vec3 *v1, const Vec3 *v2) { - return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; + return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z); } double vec3NormSq(const Vec3 *v) { return vec3Dot(v, v); } @@ -67,8 +75,6 @@ void vec3Normalize(Vec3 *v) { } double vec3DistSq(const Vec3 *v1, const Vec3 *v2) { - double dx = v1->x - v2->x; - double dy = v1->y - v2->y; - double dz = v1->z - v2->z; - return dx * dx + dy * dy + dz * dz; + Vec3 d = vec3LinComb(1.0, v1, -1.0, v2); + return vec3NormSq(&d); } From 3f21332ae939c68aaeab9c5949ba5d4173ae82a2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:17:01 -0700 Subject: [PATCH 17/91] return by value --- src/apps/miscapps/generateFaceCenterPoint.c | 3 +-- src/apps/testapps/testVec3.c | 6 ++---- src/apps/testapps/testVec3Internal.c | 9 +++------ src/h3lib/include/vec3.h | 8 ++++---- src/h3lib/lib/faceijk.c | 10 +++++----- src/h3lib/lib/h3Index.c | 5 ++--- src/h3lib/lib/vec3.c | 19 +++++++------------ 7 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index a9666bea15..e54c46a599 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -56,8 +56,7 @@ static void generate(void) { printf("static const Vec3 faceCenterPoint[NUM_ICOSA_FACES] = {\n"); for (int i = 0; i < NUM_ICOSA_FACES; i++) { LatLng centerCoords = faceCenterGeoCopy[i]; - Vec3 centerPoint; - latLngToVec3(¢erCoords, ¢erPoint); + Vec3 centerPoint = latLngToVec3(¢erCoords); printf(" {%.16f, %.16f, %.16f}, // face %2d\n", centerPoint.x, centerPoint.y, centerPoint.z, i); } diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index 4d92d21e3d..492d018c96 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -35,8 +35,7 @@ SUITE(Vec3) { TEST(crossProductOrthogonality) { Vec3 i = {.x = 1.0, .y = 0.0, .z = 0.0}; Vec3 j = {.x = 0.0, .y = 1.0, .z = 0.0}; - Vec3 k; - vec3Cross(&i, &j, &k); + Vec3 k = vec3Cross(&i, &j); t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); t_assert(fabs(k.z - 1.0) < EPSILON, "z component one"); @@ -68,8 +67,7 @@ SUITE(Vec3) { TEST(latLngToVec3_unitSphere) { LatLng geo = {.lat = 0.5, .lng = -1.3}; - Vec3 v; - latLngToVec3(&geo, &v); + Vec3 v = latLngToVec3(&geo); t_assert(fabs(vec3Norm(&v) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3Internal.c index c61b3d101e..96e01387ee 100644 --- a/src/apps/testapps/testVec3Internal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -24,20 +24,17 @@ SUITE(Vec3Internal) { Vec3 origin = {0}; LatLng c1 = {0, 0}; - Vec3 p1; - latLngToVec3(&c1, &p1); + Vec3 p1 = latLngToVec3(&c1); t_assert(fabs(vec3DistSq(&origin, &p1) - 1) < EPSILON_RAD, "Geo point is on the unit sphere"); LatLng c2 = {M_PI_2, 0}; - Vec3 p2; - latLngToVec3(&c2, &p2); + Vec3 p2 = latLngToVec3(&c2); t_assert(fabs(vec3DistSq(&p1, &p2) - 2) < EPSILON_RAD, "Geo point is on another axis"); LatLng c3 = {M_PI, 0}; - Vec3 p3; - latLngToVec3(&c3, &p3); + Vec3 p3 = latLngToVec3(&c3); t_assert(fabs(vec3DistSq(&p1, &p3) - 4) < EPSILON_RAD, "Geo point is the other side of the sphere"); } diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index 9cd6bccfbf..aed072a461 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -35,14 +35,14 @@ typedef struct { double z; ///< z component (towards north pole) } Vec3; +Vec3 latLngToVec3(const LatLng *geo); +LatLng vec3ToLatLng(const Vec3 *v); Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b); +Vec3 vec3Cross(const Vec3 *v1, const Vec3 *v2); double vec3Dot(const Vec3 *v1, const Vec3 *v2); -void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out); -void vec3Normalize(Vec3 *v); double vec3NormSq(const Vec3 *v); double vec3Norm(const Vec3 *v); +void vec3Normalize(Vec3 *v); double vec3DistSq(const Vec3 *v1, const Vec3 *v2); -void latLngToVec3(const LatLng *geo, Vec3 *v); -void vec3ToLatLng(const Vec3 *v, LatLng *geo); #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 9cfdd5288d..6b475ab9b3 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -380,7 +380,7 @@ static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { double NdotP = vec3Dot(&northPole, p); *north = vec3LinComb(1.0, &northPole, -NdotP, p); vec3Normalize(north); - vec3Cross(north, p, east); + *east = vec3Cross(north, p); } /** @@ -631,7 +631,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); Vec3 v3; _vec2ToVec3(&inter, tmpFijk.face, adjRes, 1, &v3); - vec3ToLatLng(&v3, &g->verts[g->numVerts]); + g->verts[g->numVerts] = vec3ToLatLng(&v3); g->numVerts++; } @@ -643,7 +643,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkToVec2(&fijk.coord, &vec); Vec3 v3; _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - vec3ToLatLng(&v3, &g->verts[g->numVerts]); + g->verts[g->numVerts] = vec3ToLatLng(&v3); g->numVerts++; } @@ -807,7 +807,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, if (!isIntersectionAtVertex) { Vec3 v3; _vec2ToVec3(&inter, centerIJK.face, adjRes, 1, &v3); - vec3ToLatLng(&v3, &g->verts[g->numVerts]); + g->verts[g->numVerts] = vec3ToLatLng(&v3); g->numVerts++; } } @@ -820,7 +820,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, _ijkToVec2(&fijk.coord, &vec); Vec3 v3; _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - vec3ToLatLng(&v3, &g->verts[g->numVerts]); + g->verts[g->numVerts] = vec3ToLatLng(&v3); g->numVerts++; } diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 43ff223f40..b10663c1cc 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1044,8 +1044,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { return E_LATLNG_DOMAIN; } - Vec3 v; - latLngToVec3(g, &v); + Vec3 v = latLngToVec3(g); return vec3ToCell(&v, res, out); } @@ -1179,7 +1178,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { if (e) { return e; } - vec3ToLatLng(&v, g); + *g = vec3ToLatLng(&v); return E_SUCCESS; } diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index 3dae438f24..dd9be073d9 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -29,17 +29,13 @@ * @param geo The latitude and longitude of the point. * @param v The 3D coordinate of the point. */ -void latLngToVec3(const LatLng *geo, Vec3 *v) { +Vec3 latLngToVec3(const LatLng *geo) { double r = cos(geo->lat); - - v->z = sin(geo->lat); - v->x = cos(geo->lng) * r; - v->y = sin(geo->lng) * r; + return (Vec3){cos(geo->lng) * r, sin(geo->lng) * r, sin(geo->lat)}; } -void vec3ToLatLng(const Vec3 *v, LatLng *geo) { - geo->lat = asin(v->z); - geo->lng = atan2(v->y, v->x); +LatLng vec3ToLatLng(const Vec3 *v) { + return (LatLng){asin(v->z), atan2(v->y, v->x)}; } Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b) { @@ -50,10 +46,9 @@ Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b) { }; } -void vec3Cross(const Vec3 *v1, const Vec3 *v2, Vec3 *out) { - out->x = v1->y * v2->z - v1->z * v2->y; - out->y = v1->z * v2->x - v1->x * v2->z; - out->z = v1->x * v2->y - v1->y * v2->x; +Vec3 vec3Cross(const Vec3 *v1, const Vec3 *v2) { + return (Vec3){v1->y * v2->z - v1->z * v2->y, v1->z * v2->x - v1->x * v2->z, + v1->x * v2->y - v1->y * v2->x}; } double vec3Dot(const Vec3 *v1, const Vec3 *v2) { From 8709618c09be0b777ca10bc8cea84b250b4b5eaa Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:24:58 -0700 Subject: [PATCH 18/91] pass by value --- src/apps/miscapps/generateFaceCenterPoint.c | 2 +- src/apps/testapps/testVec3.c | 20 +++++------ src/apps/testapps/testVec3Internal.c | 12 +++---- src/h3lib/include/vec3.h | 16 ++++----- src/h3lib/lib/faceijk.c | 28 +++++++-------- src/h3lib/lib/h3Index.c | 4 +-- src/h3lib/lib/vec3.c | 38 ++++++++++----------- 7 files changed, 59 insertions(+), 61 deletions(-) diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index e54c46a599..0f28579833 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -56,7 +56,7 @@ static void generate(void) { printf("static const Vec3 faceCenterPoint[NUM_ICOSA_FACES] = {\n"); for (int i = 0; i < NUM_ICOSA_FACES; i++) { LatLng centerCoords = faceCenterGeoCopy[i]; - Vec3 centerPoint = latLngToVec3(¢erCoords); + Vec3 centerPoint = latLngToVec3(centerCoords); printf(" {%.16f, %.16f, %.16f}, // face %2d\n", centerPoint.x, centerPoint.y, centerPoint.z, i); } diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index 492d018c96..1241ac43ab 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -29,28 +29,28 @@ SUITE(Vec3) { TEST(dotProduct) { Vec3 a = {.x = 1.0, .y = 0.0, .z = 0.0}; Vec3 b = {.x = -1.0, .y = 0.0, .z = 0.0}; - t_assert(vec3Dot(&a, &b) == -1.0, "dot product matches expected value"); + t_assert(vec3Dot(a, b) == -1.0, "dot product matches expected value"); } TEST(crossProductOrthogonality) { Vec3 i = {.x = 1.0, .y = 0.0, .z = 0.0}; Vec3 j = {.x = 0.0, .y = 1.0, .z = 0.0}; - Vec3 k = vec3Cross(&i, &j); + Vec3 k = vec3Cross(i, j); t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); t_assert(fabs(k.z - 1.0) < EPSILON, "z component one"); - t_assert(fabs(vec3Dot(&k, &i)) < EPSILON, "cross is orthogonal to i"); - t_assert(fabs(vec3Dot(&k, &j)) < EPSILON, "cross is orthogonal to j"); + t_assert(fabs(vec3Dot(k, i)) < EPSILON, "cross is orthogonal to i"); + t_assert(fabs(vec3Dot(k, j)) < EPSILON, "cross is orthogonal to j"); } TEST(normalizeAndMagnitude) { Vec3 v = {.x = 3.0, .y = -4.0, .z = 12.0}; - double magSq = vec3NormSq(&v); + double magSq = vec3NormSq(v); t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); - t_assert(fabs(vec3Norm(&v) - 13.0) < EPSILON, "magnitude matches"); + t_assert(fabs(vec3Norm(v) - 13.0) < EPSILON, "magnitude matches"); vec3Normalize(&v); - t_assert(fabs(vec3Norm(&v) - 1.0) < 1e-12, "normalized vector is unit"); + t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, "normalized vector is unit"); Vec3 zero = {.x = 0.0, .y = 0.0, .z = 0.0}; vec3Normalize(&zero); @@ -61,14 +61,14 @@ SUITE(Vec3) { TEST(distance) { Vec3 a = {.x = 0.0, .y = 0.0, .z = 0.0}; Vec3 b = {.x = 1.0, .y = 2.0, .z = 2.0}; - t_assert(fabs(vec3DistSq(&a, &b) - 9.0) < EPSILON, + t_assert(fabs(vec3DistSq(a, b) - 9.0) < EPSILON, "distance squared matches"); } TEST(latLngToVec3_unitSphere) { LatLng geo = {.lat = 0.5, .lng = -1.3}; - Vec3 v = latLngToVec3(&geo); - t_assert(fabs(vec3Norm(&v) - 1.0) < 1e-12, + Vec3 v = latLngToVec3(geo); + t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3Internal.c index 96e01387ee..a5b1730ea1 100644 --- a/src/apps/testapps/testVec3Internal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -24,18 +24,18 @@ SUITE(Vec3Internal) { Vec3 origin = {0}; LatLng c1 = {0, 0}; - Vec3 p1 = latLngToVec3(&c1); - t_assert(fabs(vec3DistSq(&origin, &p1) - 1) < EPSILON_RAD, + Vec3 p1 = latLngToVec3(c1); + t_assert(fabs(vec3DistSq(origin, p1) - 1) < EPSILON_RAD, "Geo point is on the unit sphere"); LatLng c2 = {M_PI_2, 0}; - Vec3 p2 = latLngToVec3(&c2); - t_assert(fabs(vec3DistSq(&p1, &p2) - 2) < EPSILON_RAD, + Vec3 p2 = latLngToVec3(c2); + t_assert(fabs(vec3DistSq(p1, p2) - 2) < EPSILON_RAD, "Geo point is on another axis"); LatLng c3 = {M_PI, 0}; - Vec3 p3 = latLngToVec3(&c3); - t_assert(fabs(vec3DistSq(&p1, &p3) - 4) < EPSILON_RAD, + Vec3 p3 = latLngToVec3(c3); + t_assert(fabs(vec3DistSq(p1, p3) - 4) < EPSILON_RAD, "Geo point is the other side of the sphere"); } } diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index aed072a461..a8b1a4f6d3 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -35,14 +35,14 @@ typedef struct { double z; ///< z component (towards north pole) } Vec3; -Vec3 latLngToVec3(const LatLng *geo); -LatLng vec3ToLatLng(const Vec3 *v); -Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b); -Vec3 vec3Cross(const Vec3 *v1, const Vec3 *v2); -double vec3Dot(const Vec3 *v1, const Vec3 *v2); -double vec3NormSq(const Vec3 *v); -double vec3Norm(const Vec3 *v); +Vec3 latLngToVec3(LatLng geo); +LatLng vec3ToLatLng(Vec3 v); +Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b); +Vec3 vec3Cross(Vec3 v1, Vec3 v2); +double vec3Dot(Vec3 v1, Vec3 v2); +double vec3NormSq(Vec3 v); +double vec3Norm(Vec3 v); void vec3Normalize(Vec3 *v); -double vec3DistSq(const Vec3 *v1, const Vec3 *v2); +double vec3DistSq(Vec3 v1, Vec3 v2); #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 6b475ab9b3..7818058738 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -377,10 +377,10 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); */ static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { Vec3 northPole = {0.0, 0.0, 1.0}; - double NdotP = vec3Dot(&northPole, p); - *north = vec3LinComb(1.0, &northPole, -NdotP, p); + double NdotP = vec3Dot(northPole, *p); + *north = vec3LinComb(1.0, northPole, -NdotP, *p); vec3Normalize(north); - *east = vec3Cross(north, p); + *east = vec3Cross(*north, *p); } /** @@ -394,12 +394,12 @@ static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { _vec3TangentBasis(p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 - double p2dotp1 = vec3Dot(p2, p1); - Vec3 p2_on_tangent = vec3LinComb(1.0, p2, -p2dotp1, p1); + double p2dotp1 = vec3Dot(*p2, *p1); + Vec3 p2_on_tangent = vec3LinComb(1.0, *p2, -p2dotp1, *p1); vec3Normalize(&p2_on_tangent); - return atan2(vec3Dot(&p2_on_tangent, &eastDir), - vec3Dot(&p2_on_tangent, &northDir)); + return atan2(vec3Dot(p2_on_tangent, eastDir), + vec3Dot(p2_on_tangent, northDir)); } /** @@ -517,9 +517,9 @@ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { // Rodrigues' rotation formula, simplified for orthogonal vectors // Direction vector D = northDir * cos(theta) + (center x northDir) * // sin(theta) where `center x northDir` is `eastDir` - Vec3 dir = vec3LinComb(cos(theta), &northDir, sin(theta), &eastDir); + Vec3 dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); - *v3 = vec3LinComb(cos(r), center, sin(r), &dir); + *v3 = vec3LinComb(cos(r), *center, sin(r), dir); vec3Normalize(v3); } @@ -631,7 +631,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); Vec3 v3; _vec2ToVec3(&inter, tmpFijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(&v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } @@ -643,7 +643,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkToVec2(&fijk.coord, &vec); Vec3 v3; _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(&v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } @@ -807,7 +807,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, if (!isIntersectionAtVertex) { Vec3 v3; _vec2ToVec3(&inter, centerIJK.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(&v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } } @@ -820,7 +820,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, _ijkToVec2(&fijk.coord, &vec); Vec3 v3; _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(&v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } @@ -998,7 +998,7 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(&faceCenterPoint[f], v3); + double sqdT = vec3DistSq(faceCenterPoint[f], *v3); if (sqdT < *sqd) { *face = f; *sqd = sqdT; diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index b10663c1cc..0a881e64cb 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1044,7 +1044,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { return E_LATLNG_DOMAIN; } - Vec3 v = latLngToVec3(g); + Vec3 v = latLngToVec3(*g); return vec3ToCell(&v, res, out); } @@ -1178,7 +1178,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { if (e) { return e; } - *g = vec3ToLatLng(&v); + *g = vec3ToLatLng(v); return E_SUCCESS; } diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index dd9be073d9..5cbe11dd6d 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -29,38 +29,36 @@ * @param geo The latitude and longitude of the point. * @param v The 3D coordinate of the point. */ -Vec3 latLngToVec3(const LatLng *geo) { - double r = cos(geo->lat); - return (Vec3){cos(geo->lng) * r, sin(geo->lng) * r, sin(geo->lat)}; +Vec3 latLngToVec3(LatLng geo) { + double r = cos(geo.lat); + return (Vec3){cos(geo.lng) * r, sin(geo.lng) * r, sin(geo.lat)}; } -LatLng vec3ToLatLng(const Vec3 *v) { - return (LatLng){asin(v->z), atan2(v->y, v->x)}; -} +LatLng vec3ToLatLng(Vec3 v) { return (LatLng){asin(v.z), atan2(v.y, v.x)}; } -Vec3 vec3LinComb(double s1, const Vec3 *a, double s2, const Vec3 *b) { +Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { return (Vec3){ - s1 * a->x + s2 * b->x, - s1 * a->y + s2 * b->y, - s1 * a->z + s2 * b->z, + s1 * a.x + s2 * b.x, + s1 * a.y + s2 * b.y, + s1 * a.z + s2 * b.z, }; } -Vec3 vec3Cross(const Vec3 *v1, const Vec3 *v2) { - return (Vec3){v1->y * v2->z - v1->z * v2->y, v1->z * v2->x - v1->x * v2->z, - v1->x * v2->y - v1->y * v2->x}; +Vec3 vec3Cross(Vec3 v1, Vec3 v2) { + return (Vec3){v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x}; } -double vec3Dot(const Vec3 *v1, const Vec3 *v2) { - return (v1->x * v2->x + v1->y * v2->y + v1->z * v2->z); +double vec3Dot(Vec3 v1, Vec3 v2) { + return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); } -double vec3NormSq(const Vec3 *v) { return vec3Dot(v, v); } +double vec3NormSq(Vec3 v) { return vec3Dot(v, v); } -double vec3Norm(const Vec3 *v) { return sqrt(vec3NormSq(v)); } +double vec3Norm(Vec3 v) { return sqrt(vec3NormSq(v)); } void vec3Normalize(Vec3 *v) { - double norm = vec3Norm(v); + double norm = vec3Norm(*v); if (norm == 0.0) return; double inv = 1.0 / norm; @@ -69,7 +67,7 @@ void vec3Normalize(Vec3 *v) { v->z *= inv; } -double vec3DistSq(const Vec3 *v1, const Vec3 *v2) { +double vec3DistSq(Vec3 v1, Vec3 v2) { Vec3 d = vec3LinComb(1.0, v1, -1.0, v2); - return vec3NormSq(&d); + return vec3NormSq(d); } From 1a50e33c9edc1fb4b35edfe05bf87ad3f90f932f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:32:11 -0700 Subject: [PATCH 19/91] clean --- src/h3lib/lib/faceijk.c | 46 +++++++++++++++++++---------------------- src/h3lib/lib/vec3.c | 27 +++++++++++++++--------- 2 files changed, 38 insertions(+), 35 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 7818058738..d29b61fea2 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -361,8 +361,27 @@ static const int unitScaleByCIIres[] = { 5764801 // res 16 }; -// Private function declarations -static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd); +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing the squared euclidean distance to that face center. + * + * @param v3 The Vec3 coordinates to encode. + * @param face The icosahedral face containing the coordinates. + * @param sqd The squared euclidean distance to its icosahedral face center. + */ +static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { + *face = 0; + // The distance between two farthest points is 2.0, therefore the square of + // the distance between two points should always be less or equal than 4.0 . + *sqd = 5.0; + for (int f = 0; f < NUM_ICOSA_FACES; ++f) { + double sqdT = vec3DistSq(faceCenterPoint[f], *v3); + if (sqdT < *sqd) { + *face = f; + *sqd = sqdT; + } + } +} /** * Compute the local north and east directions on the tangent plane @@ -982,26 +1001,3 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { } while (overage == NEW_FACE); return overage; } - -/** - * Encodes a Vec3 coordinate to the corresponding icosahedral face and - * squared euclidean distance to that face center. - * - * @param v3 The Vec3 coordinates to encode. - * @param face The icosahedral face containing the coordinates. - * @param sqd The squared euclidean distance to its icosahedral face center. - */ -static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { - // determine the icosahedron face - *face = 0; - // The distance between two farthest points is 2.0, therefore the square of - // the distance between two points should always be less or equal than 4.0 . - *sqd = 5.0; - for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(faceCenterPoint[f], *v3); - if (sqdT < *sqd) { - *face = f; - *sqd = sqdT; - } - } -} diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c index 5cbe11dd6d..0cbed3d064 100644 --- a/src/h3lib/lib/vec3.c +++ b/src/h3lib/lib/vec3.c @@ -23,18 +23,22 @@ #include "constants.h" -/** - * 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. - */ +/** Convert latitude and longitude to a unit Vec3 on the sphere. */ Vec3 latLngToVec3(LatLng geo) { double r = cos(geo.lat); - return (Vec3){cos(geo.lng) * r, sin(geo.lng) * r, sin(geo.lat)}; + return (Vec3){ + cos(geo.lng) * r, + sin(geo.lng) * r, + sin(geo.lat), + }; } -LatLng vec3ToLatLng(Vec3 v) { return (LatLng){asin(v.z), atan2(v.y, v.x)}; } +LatLng vec3ToLatLng(Vec3 v) { + return (LatLng){ + asin(v.z), + atan2(v.y, v.x), + }; +} Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { return (Vec3){ @@ -45,8 +49,11 @@ Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { } Vec3 vec3Cross(Vec3 v1, Vec3 v2) { - return (Vec3){v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x}; + return (Vec3){ + v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x, + }; } double vec3Dot(Vec3 v1, Vec3 v2) { From 2e36bd277847ce5c47e6f1ad772b895e88f29852 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:45:22 -0700 Subject: [PATCH 20/91] test for inlining --- src/h3lib/lib/faceijk.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index d29b61fea2..57f3a8f577 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -375,7 +375,10 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(faceCenterPoint[f], *v3); + double dx = faceCenterPoint[f].x - v3->x; + double dy = faceCenterPoint[f].y - v3->y; + double dz = faceCenterPoint[f].z - v3->z; + double sqdT = dx * dx + dy * dy + dz * dz; if (sqdT < *sqd) { *face = f; *sqd = sqdT; From 12c8f5d4f0f65cd0d5ee7ba4b059dfc27096ff1e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 10:54:13 -0700 Subject: [PATCH 21/91] header-only; suffering on LTO --- CMakeLists.txt | 1 - src/h3lib/include/vec3.h | 71 +++++++++++++++++++++++++++++------ src/h3lib/lib/faceijk.c | 5 +-- src/h3lib/lib/vec3.c | 80 ---------------------------------------- 4 files changed, 60 insertions(+), 97 deletions(-) delete mode 100644 src/h3lib/lib/vec3.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 29749d7fe3..814af17619 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,7 +180,6 @@ set(LIB_SOURCE_FILES src/h3lib/lib/polyfill.c src/h3lib/lib/h3Index.c src/h3lib/lib/vec2.c - src/h3lib/lib/vec3.c src/h3lib/lib/vertex.c src/h3lib/lib/linkedGeo.c src/h3lib/lib/localij.c diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index a8b1a4f6d3..6fd2c9f81b 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -20,6 +20,8 @@ #ifndef VEC3_H #define VEC3_H +#include + #include "h3api.h" #include "latLng.h" @@ -30,19 +32,64 @@ * as a unit vector in 3D Cartesian space (ECEF-like coordinates). */ typedef struct { - double x; ///< x component (towards 0deg lat, 0deg lon) - double y; ///< y component (towards 0deg lat, 90deg lon) - double z; ///< z component (towards north pole) + double x; /// towards 0deg lat, 0deg lon + double y; /// towards 0deg lat, 90deg lon + double z; /// towards north pole } Vec3; -Vec3 latLngToVec3(LatLng geo); -LatLng vec3ToLatLng(Vec3 v); -Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b); -Vec3 vec3Cross(Vec3 v1, Vec3 v2); -double vec3Dot(Vec3 v1, Vec3 v2); -double vec3NormSq(Vec3 v); -double vec3Norm(Vec3 v); -void vec3Normalize(Vec3 *v); -double vec3DistSq(Vec3 v1, Vec3 v2); +/** Convert latitude and longitude to a unit Vec3 on the sphere. */ +static inline Vec3 latLngToVec3(LatLng geo) { + double r = cos(geo.lat); + return (Vec3){ + cos(geo.lng) * r, + sin(geo.lng) * r, + sin(geo.lat), + }; +} + +static inline LatLng vec3ToLatLng(Vec3 v) { + return (LatLng){ + asin(v.z), + atan2(v.y, v.x), + }; +} + +static inline Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { + return (Vec3){ + s1 * a.x + s2 * b.x, + s1 * a.y + s2 * b.y, + s1 * a.z + s2 * b.z, + }; +} + +static inline Vec3 vec3Cross(Vec3 v1, Vec3 v2) { + return (Vec3){ + v1.y * v2.z - v1.z * v2.y, + v1.z * v2.x - v1.x * v2.z, + v1.x * v2.y - v1.y * v2.x, + }; +} + +static inline double vec3Dot(Vec3 v1, Vec3 v2) { + return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; +} + +static inline double vec3NormSq(Vec3 v) { return vec3Dot(v, v); } + +static inline double vec3Norm(Vec3 v) { return sqrt(vec3NormSq(v)); } + +static inline void vec3Normalize(Vec3 *v) { + double norm = vec3Norm(*v); + if (norm == 0.0) return; + double inv = 1.0 / norm; + v->x *= inv; + v->y *= inv; + v->z *= inv; +} + +static inline double vec3DistSq(Vec3 v1, Vec3 v2) { + Vec3 d = vec3LinComb(1.0, v1, -1.0, v2); + return vec3NormSq(d); +} #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 57f3a8f577..d29b61fea2 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -375,10 +375,7 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double dx = faceCenterPoint[f].x - v3->x; - double dy = faceCenterPoint[f].y - v3->y; - double dz = faceCenterPoint[f].z - v3->z; - double sqdT = dx * dx + dy * dy + dz * dz; + double sqdT = vec3DistSq(faceCenterPoint[f], *v3); if (sqdT < *sqd) { *face = f; *sqd = sqdT; diff --git a/src/h3lib/lib/vec3.c b/src/h3lib/lib/vec3.c deleted file mode 100644 index 0cbed3d064..0000000000 --- a/src/h3lib/lib/vec3.c +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2018, 2020-2021, 2026 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 vec3.c - * @brief 3D floating point vector functions. - */ - -#include "vec3.h" - -#include - -#include "constants.h" - -/** Convert latitude and longitude to a unit Vec3 on the sphere. */ -Vec3 latLngToVec3(LatLng geo) { - double r = cos(geo.lat); - return (Vec3){ - cos(geo.lng) * r, - sin(geo.lng) * r, - sin(geo.lat), - }; -} - -LatLng vec3ToLatLng(Vec3 v) { - return (LatLng){ - asin(v.z), - atan2(v.y, v.x), - }; -} - -Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { - return (Vec3){ - s1 * a.x + s2 * b.x, - s1 * a.y + s2 * b.y, - s1 * a.z + s2 * b.z, - }; -} - -Vec3 vec3Cross(Vec3 v1, Vec3 v2) { - return (Vec3){ - v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x, - }; -} - -double vec3Dot(Vec3 v1, Vec3 v2) { - return (v1.x * v2.x + v1.y * v2.y + v1.z * v2.z); -} - -double vec3NormSq(Vec3 v) { return vec3Dot(v, v); } - -double vec3Norm(Vec3 v) { return sqrt(vec3NormSq(v)); } - -void vec3Normalize(Vec3 *v) { - double norm = vec3Norm(*v); - if (norm == 0.0) return; - - double inv = 1.0 / norm; - v->x *= inv; - v->y *= inv; - v->z *= inv; -} - -double vec3DistSq(Vec3 v1, Vec3 v2) { - Vec3 d = vec3LinComb(1.0, v1, -1.0, v2); - return vec3NormSq(d); -} From 9916417073572975323a7f5a71b46f96b665cce3 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:07:33 -0700 Subject: [PATCH 22/91] header only --- src/h3lib/include/vec3.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3.h index 6fd2c9f81b..e3ac2723e6 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3.h @@ -15,6 +15,9 @@ */ /** @file vec3.h * @brief 3D floating point vector functions. + * + * Header-only (static inline) so callers in other translation units + * can inline these without requiring LTO. */ #ifndef VEC3_H From ad41cdea436c0ae43828a96b7031cb819ffa43a3 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:09:20 -0700 Subject: [PATCH 23/91] clean up benchmark script --- bench.py | 221 +++++++++++++++++++++++++++---------------------------- 1 file changed, 107 insertions(+), 114 deletions(-) diff --git a/bench.py b/bench.py index 4102e96181..8a51ba9d59 100644 --- a/bench.py +++ b/bench.py @@ -3,153 +3,146 @@ # /// """ -Run benchmarkCoreApi multiple times on two git refs and compare. +Benchmark latLngToCell, cellToLatLng, cellToBoundary on two git refs. -Copies the benchmark C file and CMakeLists entry to a temp location -so they survive branch switches. Restores the original branch on exit. +Checks out each ref, injects the benchmark C file if needed, builds, +runs N times, and reports the min. Restores the original branch on exit. -Usage: edit BRANCH_A, BRANCH_B, and N_RUNS below, then run with: - uv run bench.py +Usage: + uv run bench.py [ref_a] [ref_b] + +Defaults to master vs vec3d-core. """ -import subprocess -import re import os -import shutil -import tempfile - -BRANCH_A = "master" -BRANCH_B = "vec3d-core" -N_RUNS = 10 -BUILD_DIR = "build" -BENCH_BIN = "bin/benchmarkCoreApi" +import re +import subprocess +import sys ROOT = os.path.dirname(os.path.abspath(__file__)) -BENCH_SRC_REL = "src/apps/benchmarks/benchmarkCoreApi.c" -CMAKE_LINE = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" - +BUILD = os.path.join(ROOT, "build") +BENCH_SRC = "src/apps/benchmarks/benchmarkCoreApi.c" +BENCH_BIN = os.path.join(BUILD, "bin/benchmarkCoreApi") +CMAKE_ENTRY = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" +N_RUNS = 10 -def run(cmd, **kwargs): - return subprocess.run(cmd, capture_output=True, text=True, - cwd=ROOT, **kwargs) +def git(*args): + return subprocess.run( + ["git", *args], capture_output=True, text=True, cwd=ROOT + ) -def parse_output(text): - results = {} - for line in text.strip().split("\n"): - m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) - if m: - results[m.group(1)] = float(m.group(2)) - return results +def current_ref(): + r = git("rev-parse", "--abbrev-ref", "HEAD") + branch = r.stdout.strip() + r = git("rev-parse", "--short", "HEAD") + return branch, r.stdout.strip() -def run_bench(n_runs): - all_results = {} - bin_path = os.path.join(ROOT, BUILD_DIR, BENCH_BIN) - for i in range(n_runs): - r = subprocess.run([bin_path], capture_output=True, text=True) - parsed = parse_output(r.stdout) - for name, us in parsed.items(): - if name not in all_results or us < all_results[name]: - all_results[name] = us - return all_results +def checkout(ref, bench_content): + """Switch to ref, injecting benchmark file and CMakeLists entry.""" + # Clean before switching + git("checkout", "--", ".") + src = os.path.join(ROOT, BENCH_SRC) + r = git("ls-files", BENCH_SRC) + if not r.stdout.strip() and os.path.exists(src): + os.remove(src) -def setup_bench_on_branch(bench_content): - """Ensure benchmark C file and CMakeLists entry exist on current checkout.""" - bench_path = os.path.join(ROOT, BENCH_SRC_REL) - cmake_path = os.path.join(ROOT, "CMakeLists.txt") + git("checkout", ref) - os.makedirs(os.path.dirname(bench_path), exist_ok=True) - with open(bench_path, "w") as f: + # Inject benchmark file + os.makedirs(os.path.dirname(src), exist_ok=True) + with open(src, "w") as f: f.write(bench_content) - with open(cmake_path) as f: - cmake = f.read() - if "benchmarkCoreApi" not in cmake: - cmake = cmake.replace( + # Inject CMakeLists entry + cmake = os.path.join(ROOT, "CMakeLists.txt") + txt = open(cmake).read() + if "benchmarkCoreApi" not in txt: + txt = txt.replace( "add_h3_benchmark(benchmarkH3Api", - CMAKE_LINE + "\n add_h3_benchmark(benchmarkH3Api" + CMAKE_ENTRY + "\n add_h3_benchmark(benchmarkH3Api", ) - with open(cmake_path, "w") as f: - f.write(cmake) - - -def cleanup_branch(): - """Restore tracked files and remove untracked benchmark file.""" - bench_path = os.path.join(ROOT, BENCH_SRC_REL) - run(["git", "checkout", "--", "."]) - # Remove benchmark file if it's untracked (doesn't exist on this branch) - r = run(["git", "ls-files", BENCH_SRC_REL]) - if not r.stdout.strip() and os.path.exists(bench_path): - os.remove(bench_path) + open(cmake, "w").write(txt) + + +def build(): + os.makedirs(BUILD, exist_ok=True) + r = subprocess.run( + "cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", + shell=True, capture_output=True, text=True, cwd=BUILD, + ) + if r.returncode != 0: + print(f" BUILD FAILED:\n{r.stderr[-300:]}") + return False + return True + + +def bench(n_runs): + """Run benchmark n_runs times, return {name: min_us}.""" + best = {} + for _ in range(n_runs): + r = subprocess.run([BENCH_BIN], capture_output=True, text=True) + for line in r.stdout.splitlines(): + m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) + if m: + name, us = m.group(1), float(m.group(2)) + if name not in best or us < best[name]: + best[name] = us + return best + + +def bench_ref(ref, bench_content): + """Checkout ref, build, benchmark. Returns {name: min_us} or None.""" + print(f"\n{'='*50}") + print(f"Benchmarking: {ref}") + print(f"{'='*50}") + + checkout(ref, bench_content) + branch, sha = current_ref() + print(f" branch: {branch} commit: {sha}") + + if not build(): + return None + + print(f" Running {N_RUNS} iterations...") + results = bench(N_RUNS) + for name, us in results.items(): + print(f" {name}: {us:.4f} us/call") + return results def main(): - # Save current branch - r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - original_branch = r.stdout.strip() - - # Save benchmark C file content before any branch switches - bench_path = os.path.join(ROOT, BENCH_SRC_REL) - if not os.path.exists(bench_path): - print(f"Error: {BENCH_SRC_REL} not found. Run from the branch that has it.") + ref_a = sys.argv[1] if len(sys.argv) > 1 else "master" + ref_b = sys.argv[2] if len(sys.argv) > 2 else "vec3d-core" + + original, _ = current_ref() + src = os.path.join(ROOT, BENCH_SRC) + if not os.path.exists(src): + print(f"Error: {BENCH_SRC} not found.") return - with open(bench_path) as f: - bench_content = f.read() + bench_content = open(src).read() results = {} try: - for ref in [BRANCH_A, BRANCH_B]: - print(f"\n{'='*50}") - print(f"Benchmarking: {ref}") - print(f"{'='*50}") - - run(["git", "checkout", ref]) - - # Verify branch - r = run(["git", "rev-parse", "--short", "HEAD"]) - sha = r.stdout.strip() - r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - branch = r.stdout.strip() - print(f" branch: {branch} commit: {sha}") - - setup_bench_on_branch(bench_content) - - # Build - run(["mkdir", "-p", BUILD_DIR]) - r = subprocess.run( - f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", - shell=True, capture_output=True, text=True, cwd=ROOT - ) - if r.returncode != 0: - print(f"Build failed on {ref}:") - print(r.stderr[-500:]) - continue - - print(f"Running {N_RUNS} iterations...") - results[ref] = run_bench(N_RUNS) - for name, us in results[ref].items(): - print(f" {name}: {us:.4f} us/call (min of {N_RUNS})") - - cleanup_branch() + for ref in [ref_a, ref_b]: + r = bench_ref(ref, bench_content) + if r: + results[ref] = r finally: - # Always restore original branch - cleanup_branch() - run(["git", "checkout", original_branch]) - print(f"\nRestored branch: {original_branch}") + checkout(original, bench_content) + print(f"\nRestored: {original}") - # Print comparison if len(results) == 2: print(f"\n{'='*50}") print(f"Comparison (min of {N_RUNS} runs)") print(f"{'='*50}") - print(f"{'Function':<20} {BRANCH_A:>12} {BRANCH_B:>12} {'Change':>10}") + print(f"{'Function':<20} {ref_a:>12} {ref_b:>12} {'Change':>10}") print(f"{'-'*20} {'-'*12} {'-'*12} {'-'*10}") - - for name in results[BRANCH_A]: - a = results[BRANCH_A][name] - b = results[BRANCH_B].get(name) + for name in results[ref_a]: + a = results[ref_a][name] + b = results[ref_b].get(name) if b is not None: pct = (b - a) / a * 100 sign = "+" if pct > 0 else "" From b770a1cbe08de39953cc03917d622d5f47147c2a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:23:56 -0700 Subject: [PATCH 24/91] _vec3ToFaceIjk --- src/h3lib/include/faceijk.h | 2 +- src/h3lib/lib/faceijk.c | 71 +++++++++++++------------------------ src/h3lib/lib/h3Index.c | 3 +- 3 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 57f4992503..1c812244c3 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -73,7 +73,7 @@ typedef enum { // Internal functions -void _vec3ToFaceIjk(const Vec3 *p, int res, FaceIJK *h); +FaceIJK _vec3ToFaceIjk(Vec3 p, int res); void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index d29b61fea2..b0a8c2b574 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -469,42 +469,27 @@ static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { /** * Encodes a Vec3 coordinate to the FaceIJK address of the containing cell at * the specified resolution. - * - * @param p The Vec3 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 _vec3ToFaceIjk(const Vec3 *p, int res, FaceIJK *h) { - // first convert to Vec2 +FaceIJK _vec3ToFaceIjk(Vec3 p, int res) { + FaceIJK h; Vec2 v; - _vec3ToVec2(p, res, &h->face, &v); - - // then convert to ijk+ - _vec2ToCoordIJK(&v, &h->coord); + _vec3ToVec2(&p, res, &h.face, &v); + _vec2ToCoordIJK(&v, &h.coord); + return h; } /** - * Determines the center point in 3D 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 v3 The 3D coordinates of the cell center point. + * Determines the 3D coordinates of a point given by 2D hex coordinates + * on a particular icosahedral face. */ -void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { - // calculate (r, theta) in Vec2 - double r = _vec2Norm(v); +Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { + double r = _vec2Norm(&v); if (r < EPSILON) { - *v3 = faceCenterPoint[face]; - return; + return faceCenterPoint[face]; } - double theta = atan2(v->y, v->x); + double theta = atan2(v.y, v.x); // scale for current resolution length u for (int i = 0; i < res; i++) r *= M_RSQRT7; @@ -529,17 +514,15 @@ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); // now find the point at (r,theta) from the face center - const Vec3 *center = &faceCenterPoint[face]; + Vec3 center = faceCenterPoint[face]; Vec3 northDir, eastDir; - _vec3TangentBasis(center, &northDir, &eastDir); + _vec3TangentBasis(¢er, &northDir, &eastDir); - // Rodrigues' rotation formula, simplified for orthogonal vectors - // Direction vector D = northDir * cos(theta) + (center x northDir) * - // sin(theta) where `center x northDir` is `eastDir` Vec3 dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); - *v3 = vec3LinComb(cos(r), *center, sin(r), dir); - vec3Normalize(v3); + Vec3 result = vec3LinComb(cos(r), center, sin(r), dir); + vec3Normalize(&result); + return result; } /** @@ -553,7 +536,7 @@ void _vec2ToVec3(const Vec2 *v, int face, int res, int substrate, Vec3 *v3) { void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3) { Vec2 v; _ijkToVec2(&h->coord, &v); - _vec2ToVec3(&v, h->face, res, 0, v3); + *v3 = _vec2ToVec3(v, h->face, res, 0); } /** @@ -648,9 +631,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // find the intersection and add the lat/lng point to the result Vec2 inter; _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - Vec3 v3; - _vec2ToVec3(&inter, tmpFijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(v3); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(inter, tmpFijk.face, adjRes, 1)); g->numVerts++; } @@ -660,9 +642,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, if (vert < start + NUM_PENT_VERTS) { Vec2 vec; _ijkToVec2(&fijk.coord, &vec); - Vec3 v3; - _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(v3); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } @@ -824,9 +805,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, bool isIntersectionAtVertex = _vec2AlmostEquals(&orig2d0, &inter) || _vec2AlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { - Vec3 v3; - _vec2ToVec3(&inter, centerIJK.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(v3); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(inter, centerIJK.face, adjRes, 1)); g->numVerts++; } } @@ -837,9 +817,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, if (vert < start + NUM_HEX_VERTS) { Vec2 vec; _ijkToVec2(&fijk.coord, &vec); - Vec3 v3; - _vec2ToVec3(&vec, fijk.face, adjRes, 1, &v3); - g->verts[g->numVerts] = vec3ToLatLng(v3); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 0a881e64cb..4891a084be 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1067,8 +1067,7 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { return E_DOMAIN; } - FaceIJK fijk; - _vec3ToFaceIjk(v, res, &fijk); + FaceIJK fijk = _vec3ToFaceIjk(*v, res); *out = _faceIjkToCell(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; From 7977187f9456754716934dad92260fba4beff563 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:34:10 -0700 Subject: [PATCH 25/91] output params --- src/h3lib/include/faceijk.h | 2 +- src/h3lib/lib/faceijk.c | 27 ++++++++++++--------------- src/h3lib/lib/h3Index.c | 2 +- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 1c812244c3..e60e0acb35 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -74,7 +74,7 @@ typedef enum { // Internal functions FaceIJK _vec3ToFaceIjk(Vec3 p, int res); -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3); +Vec3 _faceIjkToVec3(const FaceIJK *h, int res); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index b0a8c2b574..442ae5d059 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -366,8 +366,8 @@ static const int unitScaleByCIIres[] = { * containing the squared euclidean distance to that face center. * * @param v3 The Vec3 coordinates to encode. - * @param face The icosahedral face containing the coordinates. - * @param sqd The squared euclidean distance to its icosahedral face center. + * @param face Output: the icosahedral face containing the coordinates. + * @param sqd Output: the squared euclidean distance to its face center. */ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { *face = 0; @@ -427,8 +427,8 @@ static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { * * @param p The Vec3 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. + * @param face Output: the icosahedral face containing the coordinates. + * @param v Output: the 2D hex coordinates of the cell containing the point. */ static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { // determine the icosahedron face @@ -531,12 +531,11 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. - * @param v3 The 3D coordinates of the cell center point. */ -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3) { +Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { Vec2 v; _ijkToVec2(&h->coord, &v); - *v3 = _vec2ToVec3(v, h->face, res, 0); + return _vec2ToVec3(v, h->face, res, 0); } /** @@ -547,7 +546,7 @@ void _faceIjkToVec3(const FaceIJK *h, int res, Vec3 *v3) { * @param res The H3 resolution of the cell. * @param start The first topological vertex to return. * @param length The number of topological vertexes to return. - * @param g The spherical coordinates of the cell boundary. + * @param g Output: the spherical coordinates of the cell boundary. */ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g) { @@ -655,9 +654,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, * Get the vertices of a pentagon cell as substrate FaceIJK addresses * * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. This may be adjusted if - * necessary for the substrate grid resolution. - * @param fijkVerts Output array for the vertices + * @param res In/out: the H3 resolution of the cell, adjusted for substrate. + * @param fijkVerts Output: array for the vertices. */ void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { // the vertexes of an origin-centered pentagon in a Class II resolution on a @@ -721,7 +719,7 @@ void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { * @param res The H3 resolution of the cell. * @param start The first topological vertex to return. * @param length The number of topological vertexes to return. - * @param g The spherical coordinates of the cell boundary. + * @param g Output: the spherical coordinates of the cell boundary. */ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g) { @@ -831,9 +829,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, * Get the vertices of a cell as substrate FaceIJK addresses * * @param fijk The FaceIJK address of the cell. - * @param res The H3 resolution of the cell. This may be adjusted if - * necessary for the substrate grid resolution. - * @param fijkVerts Output array for the vertices + * @param res In/out: the H3 resolution of the cell, adjusted for substrate. + * @param fijkVerts Output: array for the vertices. */ void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { // the vertexes of an origin-centered cell in a Class II resolution on a diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 4891a084be..1d02d95391 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1194,7 +1194,7 @@ H3Error cellToVec3(H3Index h3, Vec3 *v) { if (e) { return e; } - _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3), v); + *v = _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3)); return E_SUCCESS; } From 3f7dd6e3e441b1df297b329bb884225fac7e7f9d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:44:49 -0700 Subject: [PATCH 26/91] header version of _ijkToVec2 --- src/h3lib/include/coordijk.h | 7 ++++++- src/h3lib/lib/coordijk.c | 14 -------------- src/h3lib/lib/faceijk.c | 28 +++++++++------------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index a195284805..26660e5765 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -30,6 +30,7 @@ #ifndef COORDIJK_H #define COORDIJK_H +#include "constants.h" #include "h3api.h" #include "latLng.h" #include "vec2.h" @@ -88,7 +89,11 @@ typedef enum { void _setIJK(CoordIJK *ijk, int i, int j, int k); void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); -void _ijkToVec2(const CoordIJK *h, Vec2 *v); +static inline Vec2 _ijkToVec2(CoordIJK h) { + int i = h.i - h.k; + int j = h.j - h.k; + return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; +} 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); diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 1c6c52e375..8e2bdd3064 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -146,20 +146,6 @@ void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { _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 _ijkToVec2(const CoordIJK *h, Vec2 *v) { - int i = h->i - h->k; - int j = h->j - h->k; - - v->x = i - 0.5 * j; - v->y = j * M_SQRT3_2; -} - /** * Returns whether or not two ijk coordinates contain exactly the same * component values. diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 442ae5d059..15589ac24a 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -533,9 +533,7 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param res The H3 resolution of the cell. */ Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { - Vec2 v; - _ijkToVec2(&h->coord, &v); - return _vec2ToVec3(v, h->face, res, 0); + return _vec2ToVec3(_ijkToVec2(h->coord), h->face, res, 0); } /** @@ -580,8 +578,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, FaceIJK tmpFijk = fijk; - Vec2 orig2d0; - _ijkToVec2(&lastFijk.coord, &orig2d0); + Vec2 orig2d0 = _ijkToVec2(lastFijk.coord); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -599,8 +596,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2 orig2d1; - _ijkToVec2(ijk, &orig2d1); + Vec2 orig2d1 = _ijkToVec2(*ijk); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -639,10 +635,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - Vec2 vec; - _ijkToVec2(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + g->verts[g->numVerts] = vec3ToLatLng( + _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); g->numVerts++; } @@ -760,11 +754,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, fijk.face != lastFace && lastOverage != FACE_EDGE) { // find Vec2 of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2 orig2d0; - _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); + Vec2 orig2d0 = _ijkToVec2(fijkVerts[lastV].coord); - Vec2 orig2d1; - _ijkToVec2(&fijkVerts[v].coord, &orig2d1); + Vec2 orig2d1 = _ijkToVec2(fijkVerts[v].coord); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -813,10 +805,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - Vec2 vec; - _ijkToVec2(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + g->verts[g->numVerts] = vec3ToLatLng( + _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); g->numVerts++; } From e7278d9c9fd51e435e1c856420989ec81afd4bc8 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:50:42 -0700 Subject: [PATCH 27/91] revert --- src/h3lib/include/coordijk.h | 7 +------ src/h3lib/lib/coordijk.c | 14 ++++++++++++++ src/h3lib/lib/faceijk.c | 28 +++++++++++++++++++--------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 26660e5765..a195284805 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -30,7 +30,6 @@ #ifndef COORDIJK_H #define COORDIJK_H -#include "constants.h" #include "h3api.h" #include "latLng.h" #include "vec2.h" @@ -89,11 +88,7 @@ typedef enum { void _setIJK(CoordIJK *ijk, int i, int j, int k); void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); -static inline Vec2 _ijkToVec2(CoordIJK h) { - int i = h.i - h.k; - int j = h.j - h.k; - return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; -} +void _ijkToVec2(const CoordIJK *h, Vec2 *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); diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 8e2bdd3064..569340f89a 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -146,6 +146,20 @@ void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { _ijkNormalize(h); } +/** + * Find the center point in 2D cartesian coordinates of a hex. + * + * @param h The ijk coordinates of the hex. + * @param v Output: the 2D cartesian coordinates of the hex center point. + */ +void _ijkToVec2(const CoordIJK *h, Vec2 *v) { + int i = h->i - h->k; + int j = h->j - h->k; + + v->x = i - 0.5 * j; + v->y = j * M_SQRT3_2; +} + /** * Returns whether or not two ijk coordinates contain exactly the same * component values. diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 15589ac24a..442ae5d059 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -533,7 +533,9 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param res The H3 resolution of the cell. */ Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { - return _vec2ToVec3(_ijkToVec2(h->coord), h->face, res, 0); + Vec2 v; + _ijkToVec2(&h->coord, &v); + return _vec2ToVec3(v, h->face, res, 0); } /** @@ -578,7 +580,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, FaceIJK tmpFijk = fijk; - Vec2 orig2d0 = _ijkToVec2(lastFijk.coord); + Vec2 orig2d0; + _ijkToVec2(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -596,7 +599,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2 orig2d1 = _ijkToVec2(*ijk); + Vec2 orig2d1; + _ijkToVec2(ijk, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -635,8 +639,10 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - g->verts[g->numVerts] = vec3ToLatLng( - _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } @@ -754,9 +760,11 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, fijk.face != lastFace && lastOverage != FACE_EDGE) { // find Vec2 of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2 orig2d0 = _ijkToVec2(fijkVerts[lastV].coord); + Vec2 orig2d0; + _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); - Vec2 orig2d1 = _ijkToVec2(fijkVerts[v].coord); + Vec2 orig2d1; + _ijkToVec2(&fijkVerts[v].coord, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -805,8 +813,10 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - g->verts[g->numVerts] = vec3ToLatLng( - _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } From 7e9d54009db7b73145ea7a85f92db6183d3b612a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 11:58:16 -0700 Subject: [PATCH 28/91] revert --- src/h3lib/include/faceijk.h | 2 +- src/h3lib/lib/faceijk.c | 12 +++++++----- src/h3lib/lib/h3Index.c | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index e60e0acb35..c78319a084 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -73,7 +73,7 @@ typedef enum { // Internal functions -FaceIJK _vec3ToFaceIjk(Vec3 p, int res); +void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h); Vec3 _faceIjkToVec3(const FaceIJK *h, int res); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 442ae5d059..e1919ed62e 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -469,13 +469,15 @@ static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { /** * Encodes a Vec3 coordinate to the FaceIJK address of the containing cell at * the specified resolution. + * + * @param p The Vec3 coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param h Output: the FaceIJK address of the containing cell. */ -FaceIJK _vec3ToFaceIjk(Vec3 p, int res) { - FaceIJK h; +void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h) { Vec2 v; - _vec3ToVec2(&p, res, &h.face, &v); - _vec2ToCoordIJK(&v, &h.coord); - return h; + _vec3ToVec2(&p, res, &h->face, &v); + _vec2ToCoordIJK(&v, &h->coord); } /** diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 1d02d95391..8121449753 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1067,7 +1067,8 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { return E_DOMAIN; } - FaceIJK fijk = _vec3ToFaceIjk(*v, res); + FaceIJK fijk; + _vec3ToFaceIjk(*v, res, &fijk); *out = _faceIjkToCell(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; From 667090918543bbbafc2f840a7a1eb195d276a060 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 12:05:17 -0700 Subject: [PATCH 29/91] redo --- src/h3lib/include/coordijk.h | 7 ++++++- src/h3lib/lib/coordijk.c | 14 -------------- src/h3lib/lib/faceijk.c | 28 +++++++++------------------- 3 files changed, 15 insertions(+), 34 deletions(-) diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index a195284805..26660e5765 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -30,6 +30,7 @@ #ifndef COORDIJK_H #define COORDIJK_H +#include "constants.h" #include "h3api.h" #include "latLng.h" #include "vec2.h" @@ -88,7 +89,11 @@ typedef enum { void _setIJK(CoordIJK *ijk, int i, int j, int k); void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); -void _ijkToVec2(const CoordIJK *h, Vec2 *v); +static inline Vec2 _ijkToVec2(CoordIJK h) { + int i = h.i - h.k; + int j = h.j - h.k; + return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; +} 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); diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 569340f89a..8e2bdd3064 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -146,20 +146,6 @@ void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { _ijkNormalize(h); } -/** - * Find the center point in 2D cartesian coordinates of a hex. - * - * @param h The ijk coordinates of the hex. - * @param v Output: the 2D cartesian coordinates of the hex center point. - */ -void _ijkToVec2(const CoordIJK *h, Vec2 *v) { - int i = h->i - h->k; - int j = h->j - h->k; - - v->x = i - 0.5 * j; - v->y = j * M_SQRT3_2; -} - /** * Returns whether or not two ijk coordinates contain exactly the same * component values. diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index e1919ed62e..aa55c2b43d 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -535,9 +535,7 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param res The H3 resolution of the cell. */ Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { - Vec2 v; - _ijkToVec2(&h->coord, &v); - return _vec2ToVec3(v, h->face, res, 0); + return _vec2ToVec3(_ijkToVec2(h->coord), h->face, res, 0); } /** @@ -582,8 +580,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, FaceIJK tmpFijk = fijk; - Vec2 orig2d0; - _ijkToVec2(&lastFijk.coord, &orig2d0); + Vec2 orig2d0 = _ijkToVec2(lastFijk.coord); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -601,8 +598,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2 orig2d1; - _ijkToVec2(ijk, &orig2d1); + Vec2 orig2d1 = _ijkToVec2(*ijk); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -641,10 +637,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - Vec2 vec; - _ijkToVec2(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + g->verts[g->numVerts] = vec3ToLatLng( + _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); g->numVerts++; } @@ -762,11 +756,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, fijk.face != lastFace && lastOverage != FACE_EDGE) { // find Vec2 of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2 orig2d0; - _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); + Vec2 orig2d0 = _ijkToVec2(fijkVerts[lastV].coord); - Vec2 orig2d1; - _ijkToVec2(&fijkVerts[v].coord, &orig2d1); + Vec2 orig2d1 = _ijkToVec2(fijkVerts[v].coord); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -815,10 +807,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - Vec2 vec; - _ijkToVec2(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + g->verts[g->numVerts] = vec3ToLatLng( + _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); g->numVerts++; } From 0040fd6a154994ea0b976880e67e5376c23bbf2f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 12:34:57 -0700 Subject: [PATCH 30/91] inline ijk ops --- src/h3lib/include/coordijk.h | 71 ++++++++++++++++++++++--- src/h3lib/lib/coordijk.c | 100 ----------------------------------- 2 files changed, 65 insertions(+), 106 deletions(-) diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 26660e5765..61f6a3ee7a 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -87,17 +87,41 @@ typedef enum { // Internal functions -void _setIJK(CoordIJK *ijk, int i, int j, int k); +static inline void _setIJK(CoordIJK *ijk, int i, int j, int k) { + ijk->i = i; + ijk->j = j; + ijk->k = k; +} + void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); + static inline Vec2 _ijkToVec2(CoordIJK h) { int i = h.i - h.k; int j = h.j - h.k; return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; } + 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); + +static inline 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; +} + +static inline 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; +} + +static inline void _ijkScale(CoordIJK *c, int factor) { + c->i *= factor; + c->j *= factor; + c->k *= factor; +} bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk); void _ijkNormalize(CoordIJK *c); Direction _unitIjkToDigit(const CoordIJK *ijk); @@ -112,8 +136,43 @@ 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); +static inline 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; + } +} + +static inline 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; + } +} int ijkDistance(const CoordIJK *a, const CoordIJK *b); void ijkToIj(const CoordIJK *ijk, CoordIJ *ij); H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk); diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 8e2bdd3064..b83df13c59 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -32,20 +32,6 @@ #define INT32_MAX_3 (INT32_MAX / 3) -/** - * 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). @@ -158,44 +144,6 @@ 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; -} - /** * Returns true if _ijkNormalize with the given input could have a signed * integer overflow. Assumes k is set to 0. @@ -527,54 +475,6 @@ void _ijkRotate60cw(CoordIJK *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 From 64290b446dc9f524d756acaa40f1042f50bedaad Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 12:43:56 -0700 Subject: [PATCH 31/91] going header only --- CMakeLists.txt | 1 - src/h3lib/include/coordijk.h | 453 +++++++++++++++++++++++++-- src/h3lib/lib/coordijk.c | 589 ----------------------------------- 3 files changed, 430 insertions(+), 613 deletions(-) delete mode 100644 src/h3lib/lib/coordijk.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 814af17619..66049f3445 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,7 +174,6 @@ set(LIB_SOURCE_FILES src/h3lib/include/cellsToMultiPoly.h src/h3lib/lib/h3Assert.c src/h3lib/lib/algos.c - src/h3lib/lib/coordijk.c src/h3lib/lib/bbox.c src/h3lib/lib/polygon.c src/h3lib/lib/polyfill.c diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 61f6a3ee7a..bc2def1d74 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2022, 2026 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2023, 2026 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. @@ -30,11 +30,19 @@ #ifndef COORDIJK_H #define COORDIJK_H +#include +#include +#include + #include "constants.h" +#include "h3Assert.h" #include "h3api.h" #include "latLng.h" +#include "mathExtensions.h" #include "vec2.h" +#define INT32_MAX_3 (INT32_MAX / 3) + /** @struct CoordIJK * @brief IJK hexagon coordinates * @@ -93,15 +101,15 @@ static inline void _setIJK(CoordIJK *ijk, int i, int j, int k) { ijk->k = k; } -void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); - static inline Vec2 _ijkToVec2(CoordIJK h) { int i = h.i - h.k; int j = h.j - h.k; return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; } -int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2); +static inline int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2) { + return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k); +} static inline void _ijkAdd(const CoordIJK *h1, const CoordIJK *h2, CoordIJK *sum) { @@ -122,20 +130,386 @@ static inline void _ijkScale(CoordIJK *c, int factor) { c->j *= factor; c->k *= factor; } -bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk); -void _ijkNormalize(CoordIJK *c); -Direction _unitIjkToDigit(const CoordIJK *ijk); -H3Error _upAp7Checked(CoordIJK *ijk); -H3Error _upAp7rChecked(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); + +static inline bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) { + // Check for the possibility of overflow + int max, min; + if (ijk->i > ijk->j) { + max = ijk->i; + min = ijk->j; + } else { + max = ijk->j; + min = ijk->i; + } + if (min < 0) { + // Only if the min is less than 0 will the resulting number be larger + // than max. If min is positive, then max is also positive, and a + // positive signed integer minus another positive signed integer will + // not overflow. + if (ADD_INT32S_OVERFLOWS(max, min)) { + // max + min would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(0, min)) { + // 0 - INT32_MIN would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(max, min)) { + // max - min would overflow + return true; + } + } + return false; +} + +static inline 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; + } +} + +static inline void _vec2ToCoordIJK(const Vec2 *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_RSIN60; + x1 = a1 + x2 / 2.0; + + // check if we have the center of a hex + m1 = (int)x1; + m2 = (int)x2; + + // otherwise round correctly + r1 = x1 - m1; + r2 = x2 - m2; + + if (r1 < 0.5) { + if (r1 < 1.0 / 3.0) { + if (r2 < (1.0 + r1) / 2.0) { + h->i = m1; + h->j = m2; + } else { + h->i = m1; + h->j = m2 + 1; + } + } else { + if (r2 < (1.0 - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) { + h->i = m1 + 1; + } else { + h->i = m1; + } + } + } else { + if (r1 < 2.0 / 3.0) { + if (r2 < (1.0 - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) { + h->i = m1; + } else { + h->i = m1 + 1; + } + } else { + if (r2 < (r1 / 2.0)) { + 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.0) { + if ((h->j % 2) == 0) // even + { + long long int axisi = h->j / 2; + long long int diff = h->i - axisi; + h->i = (int)(h->i - 2.0 * diff); + } else { + long long int axisi = (h->j + 1) / 2; + long long int diff = h->i - axisi; + h->i = (int)(h->i - (2.0 * diff + 1)); + } + } + + if (v->y < 0.0) { + h->i = h->i - (2 * h->j + 1) / 2; + h->j = -1 * h->j; + } + + _ijkNormalize(h); +} + +static inline 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; +} + +static inline void _neighbor(CoordIJK *ijk, Direction digit) { + if (digit > CENTER_DIGIT && digit < NUM_DIGITS) { + _ijkAdd(ijk, &UNIT_VECS[digit], ijk); + _ijkNormalize(ijk); + } +} + +static inline H3Error _upAp7Checked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(i2, i)) { + return E_FAILED; + } + int i3 = i2 + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + + if (SUB_INT32S_OVERFLOWS(i3, j)) { + return E_FAILED; + } + if (ADD_INT32S_OVERFLOWS(i, j2)) { + return E_FAILED; + } + } + + ijk->i = (int)lround(((i * 3) - j) * M_ONESEVENTH); + ijk->j = (int)lround((i + (j * 2)) * M_ONESEVENTH); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +static inline H3Error _upAp7rChecked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + if (ADD_INT32S_OVERFLOWS(j2, j)) { + return E_FAILED; + } + int j3 = j2 + j; + + if (ADD_INT32S_OVERFLOWS(i2, j)) { + return E_FAILED; + } + if (SUB_INT32S_OVERFLOWS(j3, i)) { + return E_FAILED; + } + } + + ijk->i = (int)lround(((i * 2) + j) * M_ONESEVENTH); + ijk->j = (int)lround(((j * 3) - i) * M_ONESEVENTH); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +static inline void _upAp7(CoordIJK *ijk) { + // convert to CoordIJ + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + ijk->i = (int)lround((3 * i - j) * M_ONESEVENTH); + ijk->j = (int)lround((i + 2 * j) * M_ONESEVENTH); + ijk->k = 0; + _ijkNormalize(ijk); +} + +static inline void _upAp7r(CoordIJK *ijk) { + // convert to CoordIJ + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + ijk->i = (int)lround((2 * i + j) * M_ONESEVENTH); + ijk->j = (int)lround((3 * j - i) * M_ONESEVENTH); + ijk->k = 0; + _ijkNormalize(ijk); +} + +static inline 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); +} + +static inline 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); +} + +static inline 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); +} + +static inline 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); +} + +static inline 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); +} + +static inline 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); +} + static inline Direction _rotate60ccw(Direction digit) { switch (digit) { case K_AXES_DIGIT: @@ -173,10 +547,43 @@ static inline Direction _rotate60cw(Direction digit) { return digit; } } -int ijkDistance(const CoordIJK *a, const CoordIJK *b); -void ijkToIj(const CoordIJK *ijk, CoordIJ *ij); -H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk); -void ijkToCube(CoordIJK *ijk); -void cubeToIjk(CoordIJK *ijk); + +static inline 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)); +} + +static inline void ijkToIj(const CoordIJK *ijk, CoordIJ *ij) { + ij->i = ijk->i - ijk->k; + ij->j = ijk->j - ijk->k; +} + +static inline H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { + ijk->i = ij->i; + ijk->j = ij->j; + ijk->k = 0; + + if (_ijkNormalizeCouldOverflow(ijk)) { + return E_FAILED; + } + + _ijkNormalize(ijk); + return E_SUCCESS; +} + +static inline void ijkToCube(CoordIJK *ijk) { + ijk->i = -ijk->i + ijk->k; + ijk->j = ijk->j - ijk->k; + ijk->k = -ijk->i - ijk->j; +} + +static inline void cubeToIjk(CoordIJK *ijk) { + ijk->i = -ijk->i; + ijk->k = 0; + _ijkNormalize(ijk); +} #endif diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c deleted file mode 100644 index b83df13c59..0000000000 --- a/src/h3lib/lib/coordijk.c +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright 2016-2018, 2020-2023, 2026 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/lng. - */ - -#include "coordijk.h" - -#include -#include -#include -#include - -#include "constants.h" -#include "h3Assert.h" -#include "latLng.h" -#include "mathExtensions.h" - -#define INT32_MAX_3 (INT32_MAX / 3) - -/** - * 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 _vec2ToCoordIJK(const Vec2 *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_RSIN60; - x1 = a1 + x2 / 2.0; - - // check if we have the center of a hex - m1 = (int)x1; - m2 = (int)x2; - - // otherwise round correctly - r1 = x1 - m1; - r2 = x2 - m2; - - if (r1 < 0.5) { - if (r1 < 1.0 / 3.0) { - if (r2 < (1.0 + r1) / 2.0) { - h->i = m1; - h->j = m2; - } else { - h->i = m1; - h->j = m2 + 1; - } - } else { - if (r2 < (1.0 - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) { - h->i = m1 + 1; - } else { - h->i = m1; - } - } - } else { - if (r1 < 2.0 / 3.0) { - if (r2 < (1.0 - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) { - h->i = m1; - } else { - h->i = m1 + 1; - } - } else { - if (r2 < (r1 / 2.0)) { - 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.0) { - if ((h->j % 2) == 0) // even - { - long long int axisi = h->j / 2; - long long int diff = h->i - axisi; - h->i = (int)(h->i - 2.0 * diff); - } else { - long long int axisi = (h->j + 1) / 2; - long long int diff = h->i - axisi; - h->i = (int)(h->i - (2.0 * diff + 1)); - } - } - - if (v->y < 0.0) { - h->i = h->i - (2 * h->j + 1) / 2; - h->j = -1 * h->j; - } - - _ijkNormalize(h); -} - -/** - * 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); -} - -/** - * Returns true if _ijkNormalize with the given input could have a signed - * integer overflow. Assumes k is set to 0. - */ -bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) { - // Check for the possibility of overflow - int max, min; - if (ijk->i > ijk->j) { - max = ijk->i; - min = ijk->j; - } else { - max = ijk->j; - min = ijk->i; - } - if (min < 0) { - // Only if the min is less than 0 will the resulting number be larger - // than max. If min is positive, then max is also positive, and a - // positive signed integer minus another positive signed integer will - // not overflow. - if (ADD_INT32S_OVERFLOWS(max, min)) { - // max + min would overflow - return true; - } - if (SUB_INT32S_OVERFLOWS(0, min)) { - // 0 - INT32_MIN would overflow - return true; - } - if (SUB_INT32S_OVERFLOWS(max, min)) { - // max - min would overflow - return true; - } - } - return false; -} - -/** - * Normalizes ijk coordinates by setting the components to the smallest possible - * values. Works in place. - * - * This function does not protect against signed integer overflow. The caller - * must ensure that none of (i - j), (i - k), (j - i), (j - k), (k - i), (k - j) - * will overflow. This function may be changed in the future to make that check - * itself and return an error code. - * - * @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 or the zero vector - * in ijk coordinates. - * - * @param ijk The ijk coordinates; must be a unit vector or zero vector. - * @return The H3 digit (0-6) corresponding to the ijk unit vector, zero vector, - * or INVALID_DIGIT (7) 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; -} - -/** - * Returns non-zero if _upAp7 with the given input could have a signed integer - * overflow. - * - * Assumes ijk is IJK+ coordinates (no negative numbers). - */ -H3Error _upAp7Checked(CoordIJK *ijk) { - // Doesn't need to be checked because i, j, and k must all be non-negative - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - // <0 is checked because the input must all be non-negative, but some - // negative inputs are used in unit tests to exercise the below. - if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { - if (ADD_INT32S_OVERFLOWS(i, i)) { - return E_FAILED; - } - int i2 = i + i; - if (ADD_INT32S_OVERFLOWS(i2, i)) { - return E_FAILED; - } - int i3 = i2 + i; - if (ADD_INT32S_OVERFLOWS(j, j)) { - return E_FAILED; - } - int j2 = j + j; - - if (SUB_INT32S_OVERFLOWS(i3, j)) { - return E_FAILED; - } - if (ADD_INT32S_OVERFLOWS(i, j2)) { - return E_FAILED; - } - } - - ijk->i = (int)lround(((i * 3) - j) * M_ONESEVENTH); - ijk->j = (int)lround((i + (j * 2)) * M_ONESEVENTH); - ijk->k = 0; - - // Expected not to be reachable, because max + min or max - min would need - // to overflow. - if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { - return E_FAILED; - } - _ijkNormalize(ijk); - return E_SUCCESS; -} - -/** - * Returns non-zero if _upAp7r with the given input could have a signed integer - * overflow. - * - * Assumes ijk is IJK+ coordinates (no negative numbers). - */ -H3Error _upAp7rChecked(CoordIJK *ijk) { - // Doesn't need to be checked because i, j, and k must all be non-negative - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - // <0 is checked because the input must all be non-negative, but some - // negative inputs are used in unit tests to exercise the below. - if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { - if (ADD_INT32S_OVERFLOWS(i, i)) { - return E_FAILED; - } - int i2 = i + i; - if (ADD_INT32S_OVERFLOWS(j, j)) { - return E_FAILED; - } - int j2 = j + j; - if (ADD_INT32S_OVERFLOWS(j2, j)) { - return E_FAILED; - } - int j3 = j2 + j; - - if (ADD_INT32S_OVERFLOWS(i2, j)) { - return E_FAILED; - } - if (SUB_INT32S_OVERFLOWS(j3, i)) { - return E_FAILED; - } - } - - ijk->i = (int)lround(((i * 2) + j) * M_ONESEVENTH); - ijk->j = (int)lround(((j * 3) - i) * M_ONESEVENTH); - ijk->k = 0; - - // Expected not to be reachable, because max + min or max - min would need - // to overflow. - if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { - return E_FAILED; - } - _ijkNormalize(ijk); - return E_SUCCESS; -} - -/** - * 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)lround((3 * i - j) * M_ONESEVENTH); - ijk->j = (int)lround((i + 2 * j) * M_ONESEVENTH); - 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)lround((2 * i + j) * M_ONESEVENTH); - ijk->j = (int)lround((3 * j - i) * M_ONESEVENTH); - 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); -} - -/** - * 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 - * @returns E_SUCCESS on success, E_FAILED if signed integer overflow would have - * occurred. - */ -H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { - ijk->i = ij->i; - ijk->j = ij->j; - ijk->k = 0; - - if (_ijkNormalizeCouldOverflow(ijk)) { - return E_FAILED; - } - - _ijkNormalize(ijk); - return E_SUCCESS; -} - -/** - * 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); -} From 0c0171d48516e9a0225163bfda443d75eeee403e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 12:48:16 -0700 Subject: [PATCH 32/91] directedEdgeToBoundary bench --- src/apps/benchmarks/benchmarkCoreApi.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c index 9b3f9eafe0..e970c39599 100644 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -126,5 +126,31 @@ int main(void) { per_call, ITERATIONS * nCells); } + // Pre-compute directed edges for edge boundary benchmark + H3Index edges[N_POINTS * N_RESOLUTIONS]; + int nEdges = 0; + for (int c = 0; c < nCells; c++) { + H3Index out[6]; + H3_EXPORT(originToDirectedEdges)(cells[c], out); + edges[nEdges++] = out[0]; // first edge of each cell + } + + // Benchmark directedEdgeToBoundary + { + struct timespec start, end; + CellBoundary cb; + clock_gettime(CLOCK_MONOTONIC, &start); + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int e = 0; e < nEdges; e++) { + H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); + } + } + clock_gettime(CLOCK_MONOTONIC, &end); + double us = elapsed_us(&start, &end); + double per_call = us / (ITERATIONS * nEdges); + printf("directedEdgeToBoundary: %.4f us/call (%d calls)\n", + per_call, ITERATIONS * nEdges); + } + return 0; } From b106a0e01e012414d42921a8ff89cd50c2c2c049 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 13:02:08 -0700 Subject: [PATCH 33/91] justfile --- justfile | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 justfile diff --git a/justfile b/justfile new file mode 100644 index 0000000000..b869643589 --- /dev/null +++ b/justfile @@ -0,0 +1,64 @@ +init: purge + mkdir build + +build: + cd build; cmake -DCMAKE_BUILD_TYPE=Release ..; make + +purge: + rm -rf build + rm -rf *.trace + rm -rf .ipynb_checkpoints + rm -rf .cache + rm -rf .claude + +test-fast: build + cd build; make test-fast + +test-slow: build + cd build; make test + +# Run a single test binary. Dots (progress) go to /dev/null; failures print to stderr. +test-one TEST: build + ./build/bin/{{TEST}} > /dev/null + +test: + just test-fast + +bench: build + # ./build/bin/benchmarkCellsToPolyAlgos + ./build/bin/benchmarkGosperIter + +coverage: purge + mkdir build + cd build; cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..; make; make coverage + open build/coverage/index.html + +# Show uncovered lines/branches for a source file from the lcov .info data. +# Run `just coverage` first. Example: just coverage-gaps linkedGeo.c +coverage-gaps FILE: + #!/usr/bin/env bash + set -e + info="build/coverage.cleaned.info" + if [ ! -f "$info" ]; then + echo "No coverage data found — run 'just coverage' first." + exit 1 + fi + # Extract the section for this file + section=$(sed -n "/SF:.*\/{{FILE}}$/,/end_of_record/p" "$info") + if [ -z "$section" ]; then + echo "File {{FILE}} not found in coverage data." + echo "Available files:" + grep '^SF:' "$info" | sed 's|.*src/h3lib/||' + exit 1 + fi + echo "=== Summary ===" + lf=$(echo "$section" | grep '^LF:' | cut -d: -f2) + lh=$(echo "$section" | grep '^LH:' | cut -d: -f2) + brf=$(echo "$section" | grep '^BRF:' | cut -d: -f2) + brh=$(echo "$section" | grep '^BRH:' | cut -d: -f2) + echo "Lines: $lh/$lf Branches: $brh/$brf" + echo "" + echo "=== Uncovered lines (DA:line,0) ===" + echo "$section" | grep '^DA:' | awk -F'[,:]' '$3 == 0 {print " line " $2}' || echo " (none)" + echo "" + echo "=== Untaken branches (BRDA:line,block,branch,0) ===" From 421eca48ec563a835b21f483d3f419086b559b41 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 13:18:06 -0700 Subject: [PATCH 34/91] revert coorijk header change --- CMakeLists.txt | 1 + src/h3lib/include/coordijk.h | 529 ++------------------------ src/h3lib/lib/coordijk.c | 703 +++++++++++++++++++++++++++++++++++ src/h3lib/lib/faceijk.c | 28 +- 4 files changed, 752 insertions(+), 509 deletions(-) create mode 100644 src/h3lib/lib/coordijk.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 66049f3445..d0acfc57ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -179,6 +179,7 @@ set(LIB_SOURCE_FILES src/h3lib/lib/polyfill.c src/h3lib/lib/h3Index.c src/h3lib/lib/vec2.c + src/h3lib/lib/coordijk.c src/h3lib/lib/vertex.c src/h3lib/lib/linkedGeo.c src/h3lib/lib/localij.c diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index bc2def1d74..a195284805 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2023, 2026 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2022, 2026 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. @@ -30,19 +30,10 @@ #ifndef COORDIJK_H #define COORDIJK_H -#include -#include -#include - -#include "constants.h" -#include "h3Assert.h" #include "h3api.h" #include "latLng.h" -#include "mathExtensions.h" #include "vec2.h" -#define INT32_MAX_3 (INT32_MAX / 3) - /** @struct CoordIJK * @brief IJK hexagon coordinates * @@ -95,495 +86,33 @@ typedef enum { // Internal functions -static inline void _setIJK(CoordIJK *ijk, int i, int j, int k) { - ijk->i = i; - ijk->j = j; - ijk->k = k; -} - -static inline Vec2 _ijkToVec2(CoordIJK h) { - int i = h.i - h.k; - int j = h.j - h.k; - return (Vec2){i - 0.5 * j, j * M_SQRT3_2}; -} - -static inline int _ijkMatches(const CoordIJK *c1, const CoordIJK *c2) { - return (c1->i == c2->i && c1->j == c2->j && c1->k == c2->k); -} - -static inline 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; -} - -static inline 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; -} - -static inline void _ijkScale(CoordIJK *c, int factor) { - c->i *= factor; - c->j *= factor; - c->k *= factor; -} - -static inline bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) { - // Check for the possibility of overflow - int max, min; - if (ijk->i > ijk->j) { - max = ijk->i; - min = ijk->j; - } else { - max = ijk->j; - min = ijk->i; - } - if (min < 0) { - // Only if the min is less than 0 will the resulting number be larger - // than max. If min is positive, then max is also positive, and a - // positive signed integer minus another positive signed integer will - // not overflow. - if (ADD_INT32S_OVERFLOWS(max, min)) { - // max + min would overflow - return true; - } - if (SUB_INT32S_OVERFLOWS(0, min)) { - // 0 - INT32_MIN would overflow - return true; - } - if (SUB_INT32S_OVERFLOWS(max, min)) { - // max - min would overflow - return true; - } - } - return false; -} - -static inline 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; - } -} - -static inline void _vec2ToCoordIJK(const Vec2 *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_RSIN60; - x1 = a1 + x2 / 2.0; - - // check if we have the center of a hex - m1 = (int)x1; - m2 = (int)x2; - - // otherwise round correctly - r1 = x1 - m1; - r2 = x2 - m2; - - if (r1 < 0.5) { - if (r1 < 1.0 / 3.0) { - if (r2 < (1.0 + r1) / 2.0) { - h->i = m1; - h->j = m2; - } else { - h->i = m1; - h->j = m2 + 1; - } - } else { - if (r2 < (1.0 - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) { - h->i = m1 + 1; - } else { - h->i = m1; - } - } - } else { - if (r1 < 2.0 / 3.0) { - if (r2 < (1.0 - r1)) { - h->j = m2; - } else { - h->j = m2 + 1; - } - - if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) { - h->i = m1; - } else { - h->i = m1 + 1; - } - } else { - if (r2 < (r1 / 2.0)) { - 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.0) { - if ((h->j % 2) == 0) // even - { - long long int axisi = h->j / 2; - long long int diff = h->i - axisi; - h->i = (int)(h->i - 2.0 * diff); - } else { - long long int axisi = (h->j + 1) / 2; - long long int diff = h->i - axisi; - h->i = (int)(h->i - (2.0 * diff + 1)); - } - } - - if (v->y < 0.0) { - h->i = h->i - (2 * h->j + 1) / 2; - h->j = -1 * h->j; - } - - _ijkNormalize(h); -} - -static inline 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; -} - -static inline void _neighbor(CoordIJK *ijk, Direction digit) { - if (digit > CENTER_DIGIT && digit < NUM_DIGITS) { - _ijkAdd(ijk, &UNIT_VECS[digit], ijk); - _ijkNormalize(ijk); - } -} - -static inline H3Error _upAp7Checked(CoordIJK *ijk) { - // Doesn't need to be checked because i, j, and k must all be non-negative - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - // <0 is checked because the input must all be non-negative, but some - // negative inputs are used in unit tests to exercise the below. - if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { - if (ADD_INT32S_OVERFLOWS(i, i)) { - return E_FAILED; - } - int i2 = i + i; - if (ADD_INT32S_OVERFLOWS(i2, i)) { - return E_FAILED; - } - int i3 = i2 + i; - if (ADD_INT32S_OVERFLOWS(j, j)) { - return E_FAILED; - } - int j2 = j + j; - - if (SUB_INT32S_OVERFLOWS(i3, j)) { - return E_FAILED; - } - if (ADD_INT32S_OVERFLOWS(i, j2)) { - return E_FAILED; - } - } - - ijk->i = (int)lround(((i * 3) - j) * M_ONESEVENTH); - ijk->j = (int)lround((i + (j * 2)) * M_ONESEVENTH); - ijk->k = 0; - - // Expected not to be reachable, because max + min or max - min would need - // to overflow. - if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { - return E_FAILED; - } - _ijkNormalize(ijk); - return E_SUCCESS; -} - -static inline H3Error _upAp7rChecked(CoordIJK *ijk) { - // Doesn't need to be checked because i, j, and k must all be non-negative - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - // <0 is checked because the input must all be non-negative, but some - // negative inputs are used in unit tests to exercise the below. - if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { - if (ADD_INT32S_OVERFLOWS(i, i)) { - return E_FAILED; - } - int i2 = i + i; - if (ADD_INT32S_OVERFLOWS(j, j)) { - return E_FAILED; - } - int j2 = j + j; - if (ADD_INT32S_OVERFLOWS(j2, j)) { - return E_FAILED; - } - int j3 = j2 + j; - - if (ADD_INT32S_OVERFLOWS(i2, j)) { - return E_FAILED; - } - if (SUB_INT32S_OVERFLOWS(j3, i)) { - return E_FAILED; - } - } - - ijk->i = (int)lround(((i * 2) + j) * M_ONESEVENTH); - ijk->j = (int)lround(((j * 3) - i) * M_ONESEVENTH); - ijk->k = 0; - - // Expected not to be reachable, because max + min or max - min would need - // to overflow. - if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { - return E_FAILED; - } - _ijkNormalize(ijk); - return E_SUCCESS; -} - -static inline void _upAp7(CoordIJK *ijk) { - // convert to CoordIJ - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - ijk->i = (int)lround((3 * i - j) * M_ONESEVENTH); - ijk->j = (int)lround((i + 2 * j) * M_ONESEVENTH); - ijk->k = 0; - _ijkNormalize(ijk); -} - -static inline void _upAp7r(CoordIJK *ijk) { - // convert to CoordIJ - int i = ijk->i - ijk->k; - int j = ijk->j - ijk->k; - - ijk->i = (int)lround((2 * i + j) * M_ONESEVENTH); - ijk->j = (int)lround((3 * j - i) * M_ONESEVENTH); - ijk->k = 0; - _ijkNormalize(ijk); -} - -static inline 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); -} - -static inline 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); -} - -static inline 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); -} - -static inline 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); -} - -static inline 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); -} - -static inline 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); -} - -static inline 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; - } -} - -static inline 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; - } -} - -static inline 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)); -} - -static inline void ijkToIj(const CoordIJK *ijk, CoordIJ *ij) { - ij->i = ijk->i - ijk->k; - ij->j = ijk->j - ijk->k; -} - -static inline H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { - ijk->i = ij->i; - ijk->j = ij->j; - ijk->k = 0; - - if (_ijkNormalizeCouldOverflow(ijk)) { - return E_FAILED; - } - - _ijkNormalize(ijk); - return E_SUCCESS; -} - -static inline void ijkToCube(CoordIJK *ijk) { - ijk->i = -ijk->i + ijk->k; - ijk->j = ijk->j - ijk->k; - ijk->k = -ijk->i - ijk->j; -} - -static inline void cubeToIjk(CoordIJK *ijk) { - ijk->i = -ijk->i; - ijk->k = 0; - _ijkNormalize(ijk); -} +void _setIJK(CoordIJK *ijk, int i, int j, int k); +void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); +void _ijkToVec2(const CoordIJK *h, Vec2 *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); +bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk); +void _ijkNormalize(CoordIJK *c); +Direction _unitIjkToDigit(const CoordIJK *ijk); +H3Error _upAp7Checked(CoordIJK *ijk); +H3Error _upAp7rChecked(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); +H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk); +void ijkToCube(CoordIJK *ijk); +void cubeToIjk(CoordIJK *ijk); #endif diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c new file mode 100644 index 0000000000..1c6c52e375 --- /dev/null +++ b/src/h3lib/lib/coordijk.c @@ -0,0 +1,703 @@ +/* + * Copyright 2016-2018, 2020-2023, 2026 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/lng. + */ + +#include "coordijk.h" + +#include +#include +#include +#include + +#include "constants.h" +#include "h3Assert.h" +#include "latLng.h" +#include "mathExtensions.h" + +#define INT32_MAX_3 (INT32_MAX / 3) + +/** + * 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 _vec2ToCoordIJK(const Vec2 *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_RSIN60; + x1 = a1 + x2 / 2.0; + + // check if we have the center of a hex + m1 = (int)x1; + m2 = (int)x2; + + // otherwise round correctly + r1 = x1 - m1; + r2 = x2 - m2; + + if (r1 < 0.5) { + if (r1 < 1.0 / 3.0) { + if (r2 < (1.0 + r1) / 2.0) { + h->i = m1; + h->j = m2; + } else { + h->i = m1; + h->j = m2 + 1; + } + } else { + if (r2 < (1.0 - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((1.0 - r1) <= r2 && r2 < (2.0 * r1)) { + h->i = m1 + 1; + } else { + h->i = m1; + } + } + } else { + if (r1 < 2.0 / 3.0) { + if (r2 < (1.0 - r1)) { + h->j = m2; + } else { + h->j = m2 + 1; + } + + if ((2.0 * r1 - 1.0) < r2 && r2 < (1.0 - r1)) { + h->i = m1; + } else { + h->i = m1 + 1; + } + } else { + if (r2 < (r1 / 2.0)) { + 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.0) { + if ((h->j % 2) == 0) // even + { + long long int axisi = h->j / 2; + long long int diff = h->i - axisi; + h->i = (int)(h->i - 2.0 * diff); + } else { + long long int axisi = (h->j + 1) / 2; + long long int diff = h->i - axisi; + h->i = (int)(h->i - (2.0 * diff + 1)); + } + } + + if (v->y < 0.0) { + 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 _ijkToVec2(const CoordIJK *h, Vec2 *v) { + int i = h->i - h->k; + int j = h->j - h->k; + + v->x = i - 0.5 * 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; +} + +/** + * Returns true if _ijkNormalize with the given input could have a signed + * integer overflow. Assumes k is set to 0. + */ +bool _ijkNormalizeCouldOverflow(const CoordIJK *ijk) { + // Check for the possibility of overflow + int max, min; + if (ijk->i > ijk->j) { + max = ijk->i; + min = ijk->j; + } else { + max = ijk->j; + min = ijk->i; + } + if (min < 0) { + // Only if the min is less than 0 will the resulting number be larger + // than max. If min is positive, then max is also positive, and a + // positive signed integer minus another positive signed integer will + // not overflow. + if (ADD_INT32S_OVERFLOWS(max, min)) { + // max + min would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(0, min)) { + // 0 - INT32_MIN would overflow + return true; + } + if (SUB_INT32S_OVERFLOWS(max, min)) { + // max - min would overflow + return true; + } + } + return false; +} + +/** + * Normalizes ijk coordinates by setting the components to the smallest possible + * values. Works in place. + * + * This function does not protect against signed integer overflow. The caller + * must ensure that none of (i - j), (i - k), (j - i), (j - k), (k - i), (k - j) + * will overflow. This function may be changed in the future to make that check + * itself and return an error code. + * + * @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 or the zero vector + * in ijk coordinates. + * + * @param ijk The ijk coordinates; must be a unit vector or zero vector. + * @return The H3 digit (0-6) corresponding to the ijk unit vector, zero vector, + * or INVALID_DIGIT (7) 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; +} + +/** + * Returns non-zero if _upAp7 with the given input could have a signed integer + * overflow. + * + * Assumes ijk is IJK+ coordinates (no negative numbers). + */ +H3Error _upAp7Checked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(i2, i)) { + return E_FAILED; + } + int i3 = i2 + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + + if (SUB_INT32S_OVERFLOWS(i3, j)) { + return E_FAILED; + } + if (ADD_INT32S_OVERFLOWS(i, j2)) { + return E_FAILED; + } + } + + ijk->i = (int)lround(((i * 3) - j) * M_ONESEVENTH); + ijk->j = (int)lround((i + (j * 2)) * M_ONESEVENTH); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * Returns non-zero if _upAp7r with the given input could have a signed integer + * overflow. + * + * Assumes ijk is IJK+ coordinates (no negative numbers). + */ +H3Error _upAp7rChecked(CoordIJK *ijk) { + // Doesn't need to be checked because i, j, and k must all be non-negative + int i = ijk->i - ijk->k; + int j = ijk->j - ijk->k; + + // <0 is checked because the input must all be non-negative, but some + // negative inputs are used in unit tests to exercise the below. + if (i >= INT32_MAX_3 || j >= INT32_MAX_3 || i < 0 || j < 0) { + if (ADD_INT32S_OVERFLOWS(i, i)) { + return E_FAILED; + } + int i2 = i + i; + if (ADD_INT32S_OVERFLOWS(j, j)) { + return E_FAILED; + } + int j2 = j + j; + if (ADD_INT32S_OVERFLOWS(j2, j)) { + return E_FAILED; + } + int j3 = j2 + j; + + if (ADD_INT32S_OVERFLOWS(i2, j)) { + return E_FAILED; + } + if (SUB_INT32S_OVERFLOWS(j3, i)) { + return E_FAILED; + } + } + + ijk->i = (int)lround(((i * 2) + j) * M_ONESEVENTH); + ijk->j = (int)lround(((j * 3) - i) * M_ONESEVENTH); + ijk->k = 0; + + // Expected not to be reachable, because max + min or max - min would need + // to overflow. + if (NEVER(_ijkNormalizeCouldOverflow(ijk))) { + return E_FAILED; + } + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * 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)lround((3 * i - j) * M_ONESEVENTH); + ijk->j = (int)lround((i + 2 * j) * M_ONESEVENTH); + 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)lround((2 * i + j) * M_ONESEVENTH); + ijk->j = (int)lround((3 * j - i) * M_ONESEVENTH); + 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 + * @returns E_SUCCESS on success, E_FAILED if signed integer overflow would have + * occurred. + */ +H3Error ijToIjk(const CoordIJ *ij, CoordIJK *ijk) { + ijk->i = ij->i; + ijk->j = ij->j; + ijk->k = 0; + + if (_ijkNormalizeCouldOverflow(ijk)) { + return E_FAILED; + } + + _ijkNormalize(ijk); + return E_SUCCESS; +} + +/** + * Convert IJK coordinates to cube coordinates, in place + * @param ijk Coordinate to convert + */ +void ijkToCube(CoordIJK *ijk) { + ijk->i = -ijk->i + ijk->k; + ijk->j = ijk->j - ijk->k; + ijk->k = -ijk->i - ijk->j; +} + +/** + * Convert cube coordinates to IJK coordinates, in place + * @param ijk Coordinate to convert + */ +void cubeToIjk(CoordIJK *ijk) { + ijk->i = -ijk->i; + ijk->k = 0; + _ijkNormalize(ijk); +} diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index aa55c2b43d..e1919ed62e 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -535,7 +535,9 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param res The H3 resolution of the cell. */ Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { - return _vec2ToVec3(_ijkToVec2(h->coord), h->face, res, 0); + Vec2 v; + _ijkToVec2(&h->coord, &v); + return _vec2ToVec3(v, h->face, res, 0); } /** @@ -580,7 +582,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, FaceIJK tmpFijk = fijk; - Vec2 orig2d0 = _ijkToVec2(lastFijk.coord); + Vec2 orig2d0; + _ijkToVec2(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -598,7 +601,8 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2 orig2d1 = _ijkToVec2(*ijk); + Vec2 orig2d1; + _ijkToVec2(ijk, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -637,8 +641,10 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - g->verts[g->numVerts] = vec3ToLatLng( - _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } @@ -756,9 +762,11 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, fijk.face != lastFace && lastOverage != FACE_EDGE) { // find Vec2 of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2 orig2d0 = _ijkToVec2(fijkVerts[lastV].coord); + Vec2 orig2d0; + _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); - Vec2 orig2d1 = _ijkToVec2(fijkVerts[v].coord); + Vec2 orig2d1; + _ijkToVec2(&fijkVerts[v].coord, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -807,8 +815,10 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - g->verts[g->numVerts] = vec3ToLatLng( - _vec2ToVec3(_ijkToVec2(fijk.coord), fijk.face, adjRes, 1)); + Vec2 vec; + _ijkToVec2(&fijk.coord, &vec); + g->verts[g->numVerts] = + vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } From 397748a7a8f13156c2654795f44ac33a4cc419a5 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 13:23:03 -0700 Subject: [PATCH 35/91] comments --- src/h3lib/include/vec2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h3lib/include/vec2.h b/src/h3lib/include/vec2.h index 158b4931d8..5a1c1a3fec 100644 --- a/src/h3lib/include/vec2.h +++ b/src/h3lib/include/vec2.h @@ -30,8 +30,8 @@ * x-axis aligned to the face's i-axis and y perpendicular to it. */ typedef struct { - double x; ///< x component (aligned with face i-axis) - double y; ///< y component (perpendicular to face i-axis) + double x; /// aligned with face i-axis + double y; /// perpendicular to face i-axis } Vec2; // Internal functions From 038201522a5c6803c0f979feee3a422d0d9a1f23 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 13:42:16 -0700 Subject: [PATCH 36/91] update benchmarks --- CMakeLists.txt | 2 + src/apps/benchmarks/benchmarkCoreApi.c | 158 ++++++++----------------- src/apps/benchmarks/dumpCoreApi.c | 3 +- 3 files changed, 52 insertions(+), 111 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0acfc57ee..de95c90baa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,8 @@ set(OTHER_SOURCE_FILES src/apps/benchmarks/benchmarkVertex.c src/apps/benchmarks/benchmarkIsValidCell.c src/apps/benchmarks/benchmarkH3Api.c + src/apps/benchmarks/benchmarkCoreApi.c + src/apps/benchmarks/dumpCoreApi.c src/apps/benchmarks/benchmarkArea.c) set(ALL_SOURCE_FILES diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c index e970c39599..3629157035 100644 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -16,13 +16,11 @@ /** * @file benchmarkCoreApi.c - * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary with - * diverse inputs across faces and resolutions. + * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary, + * directedEdgeToBoundary with diverse inputs. */ -#include -#include - +#include "benchmark.h" #include "h3api.h" #define N_POINTS 20 @@ -31,126 +29,68 @@ // Points spread across the globe (hitting different icosahedron faces) static const LatLng points[N_POINTS] = { - {0.659966917655, -2.1364398519396}, // San Francisco area - {0.8527087756, -0.0405865662}, // London area - {0.6234025842, 2.0075945568}, // Tokyo area - {-0.5934119457, 2.5368879644}, // Sydney area - {0.4799655443, 0.6457718232}, // Cairo area - {-0.4014257280, -0.7610418886}, // São Paulo area - {0.9679776674, -1.7453292520}, // Alaska - {-1.2217304764, 0.0000000000}, // near south pole - {1.2217304764, 0.0000000000}, // near north pole - {0.0000000000, 0.0000000000}, // null island - {0.0000000000, 3.1415926536}, // antimeridian - {0.7853981634, 1.5707963268}, // 45N 90E - {-0.7853981634, -1.5707963268}, // 45S 90W - {0.3490658504, -1.2217304764}, // 20N 70W - {-0.1745329252, 0.5235987756}, // 10S 30E - {1.0471975512, -0.5235987756}, // 60N 30W - {-1.0471975512, 2.0943951024}, // 60S 120E - {0.2617993878, 1.8325957146}, // 15N 105E - {-0.8726646260, -1.0471975512}, // 50S 60W - {0.5235987756, -2.6179938780}, // 30N 150W + {0.659966917655, -2.1364398519396}, {0.8527087756, -0.0405865662}, + {0.6234025842, 2.0075945568}, {-0.5934119457, 2.5368879644}, + {0.4799655443, 0.6457718232}, {-0.4014257280, -0.7610418886}, + {0.9679776674, -1.7453292520}, {-1.2217304764, 0.0000000000}, + {1.2217304764, 0.0000000000}, {0.0000000000, 0.0000000000}, + {0.0000000000, 3.1415926536}, {0.7853981634, 1.5707963268}, + {-0.7853981634, -1.5707963268}, {0.3490658504, -1.2217304764}, + {-0.1745329252, 0.5235987756}, {1.0471975512, -0.5235987756}, + {-1.0471975512, 2.0943951024}, {0.2617993878, 1.8325957146}, + {-0.8726646260, -1.0471975512}, {0.5235987756, -2.6179938780}, }; static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; -static double elapsed_us(struct timespec *start, struct timespec *end) { - double sec = end->tv_sec - start->tv_sec; - double nsec = end->tv_nsec - start->tv_nsec; - return (sec * 1e9 + nsec) / 1e3; -} +H3Index cells[N_POINTS * N_RESOLUTIONS]; +int nCells = 0; +H3Index edges[N_POINTS * N_RESOLUTIONS]; +int nEdges = 0; -int main(void) { - H3Index cells[N_POINTS * N_RESOLUTIONS]; - int nCells = 0; +BEGIN_BENCHMARKS(); - // Pre-compute cells for inverse benchmarks - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); - nCells++; - } +// Pre-compute cells and edges +for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); + nCells++; } +} +for (int c = 0; c < nCells; c++) { + H3Index out[6]; + H3_EXPORT(originToDirectedEdges)(cells[c], out); + edges[nEdges++] = out[0]; +} - // Benchmark latLngToCell - { - struct timespec start, end; - H3Index h; - clock_gettime(CLOCK_MONOTONIC, &start); - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); - } - } - } - clock_gettime(CLOCK_MONOTONIC, &end); - double us = elapsed_us(&start, &end); - double per_call = us / (ITERATIONS * N_POINTS * N_RESOLUTIONS); - printf("latLngToCell: %.4f us/call (%d calls)\n", - per_call, ITERATIONS * N_POINTS * N_RESOLUTIONS); - } +H3Index h; +LatLng outCoord; +CellBoundary cb; - // Benchmark cellToLatLng - { - struct timespec start, end; - LatLng out; - clock_gettime(CLOCK_MONOTONIC, &start); - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToLatLng)(cells[c], &out); - } +BENCHMARK(latLngToCell, ITERATIONS, { + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); } - clock_gettime(CLOCK_MONOTONIC, &end); - double us = elapsed_us(&start, &end); - double per_call = us / (ITERATIONS * nCells); - printf("cellToLatLng: %.4f us/call (%d calls)\n", - per_call, ITERATIONS * nCells); } +}); - // Benchmark cellToBoundary - { - struct timespec start, end; - CellBoundary cb; - clock_gettime(CLOCK_MONOTONIC, &start); - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToBoundary)(cells[c], &cb); - } - } - clock_gettime(CLOCK_MONOTONIC, &end); - double us = elapsed_us(&start, &end); - double per_call = us / (ITERATIONS * nCells); - printf("cellToBoundary: %.4f us/call (%d calls)\n", - per_call, ITERATIONS * nCells); +BENCHMARK(cellToLatLng, ITERATIONS, { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToLatLng)(cells[c], &outCoord); } +}); - // Pre-compute directed edges for edge boundary benchmark - H3Index edges[N_POINTS * N_RESOLUTIONS]; - int nEdges = 0; +BENCHMARK(cellToBoundary, ITERATIONS, { for (int c = 0; c < nCells; c++) { - H3Index out[6]; - H3_EXPORT(originToDirectedEdges)(cells[c], out); - edges[nEdges++] = out[0]; // first edge of each cell + H3_EXPORT(cellToBoundary)(cells[c], &cb); } +}); - // Benchmark directedEdgeToBoundary - { - struct timespec start, end; - CellBoundary cb; - clock_gettime(CLOCK_MONOTONIC, &start); - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int e = 0; e < nEdges; e++) { - H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); - } - } - clock_gettime(CLOCK_MONOTONIC, &end); - double us = elapsed_us(&start, &end); - double per_call = us / (ITERATIONS * nEdges); - printf("directedEdgeToBoundary: %.4f us/call (%d calls)\n", - per_call, ITERATIONS * nEdges); +BENCHMARK(directedEdgeToBoundary, ITERATIONS, { + for (int e = 0; e < nEdges; e++) { + H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); } +}); - return 0; -} +END_BENCHMARKS(); diff --git a/src/apps/benchmarks/dumpCoreApi.c b/src/apps/benchmarks/dumpCoreApi.c index 80c8017862..390fd3f169 100644 --- a/src/apps/benchmarks/dumpCoreApi.c +++ b/src/apps/benchmarks/dumpCoreApi.c @@ -43,8 +43,7 @@ static void dumpLatLngToCell(double lat, double lng) { for (int r = 0; r < (int)N_RES; r++) { H3Index h; H3_EXPORT(latLngToCell)(&g, testResolutions[r], &h); - printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, - testResolutions[r], h); + printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, testResolutions[r], h); } } From e834f8cc6ddea7c62d6c65e1e791e682e2e6de8e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 13:44:17 -0700 Subject: [PATCH 37/91] again --- src/apps/benchmarks/benchmarkCoreApi.c | 116 +++++++++++++++++-------- 1 file changed, 78 insertions(+), 38 deletions(-) diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c index 3629157035..4c2ae904b4 100644 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -18,8 +18,14 @@ * @file benchmarkCoreApi.c * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary, * directedEdgeToBoundary with diverse inputs. + * + * Uses START_TIMER/END_TIMER from benchmark.h for cross-platform timing, + * but reports per-call microseconds (not per-iteration) for use with + * bench.py. */ +#include + #include "benchmark.h" #include "h3api.h" @@ -43,54 +49,88 @@ static const LatLng points[N_POINTS] = { static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; -H3Index cells[N_POINTS * N_RESOLUTIONS]; -int nCells = 0; -H3Index edges[N_POINTS * N_RESOLUTIONS]; -int nEdges = 0; - -BEGIN_BENCHMARKS(); +int main(void) { + H3Index cells[N_POINTS * N_RESOLUTIONS]; + int nCells = 0; -// Pre-compute cells and edges -for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); - nCells++; - } -} -for (int c = 0; c < nCells; c++) { - H3Index out[6]; - H3_EXPORT(originToDirectedEdges)(cells[c], out); - edges[nEdges++] = out[0]; -} - -H3Index h; -LatLng outCoord; -CellBoundary cb; - -BENCHMARK(latLngToCell, ITERATIONS, { + // Pre-compute cells for inverse benchmarks for (int r = 0; r < N_RESOLUTIONS; r++) { for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); + nCells++; } } -}); -BENCHMARK(cellToLatLng, ITERATIONS, { + // Pre-compute directed edges + H3Index edges[N_POINTS * N_RESOLUTIONS]; + int nEdges = 0; for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToLatLng)(cells[c], &outCoord); + H3Index out[6]; + H3_EXPORT(originToDirectedEdges)(cells[c], out); + edges[nEdges++] = out[0]; } -}); -BENCHMARK(cellToBoundary, ITERATIONS, { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToBoundary)(cells[c], &cb); + // Benchmark latLngToCell + { + H3Index h; + int totalCalls = ITERATIONS * N_POINTS * N_RESOLUTIONS; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); + } + } + } + END_TIMER(duration); + printf("latLngToCell: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); } -}); -BENCHMARK(directedEdgeToBoundary, ITERATIONS, { - for (int e = 0; e < nEdges; e++) { - H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); + // Benchmark cellToLatLng + { + LatLng out; + int totalCalls = ITERATIONS * nCells; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToLatLng)(cells[c], &out); + } + } + END_TIMER(duration); + printf("cellToLatLng: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + // Benchmark cellToBoundary + { + CellBoundary cb; + int totalCalls = ITERATIONS * nCells; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToBoundary)(cells[c], &cb); + } + } + END_TIMER(duration); + printf("cellToBoundary: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); } -}); -END_BENCHMARKS(); + // Benchmark directedEdgeToBoundary + { + CellBoundary cb; + int totalCalls = ITERATIONS * nEdges; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int e = 0; e < nEdges; e++) { + H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); + } + } + END_TIMER(duration); + printf("directedEdgeToBoundary: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + return 0; +} From b8194f3915e796680e5af6666b69f73e9c6ae91e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 14:09:21 -0700 Subject: [PATCH 38/91] reduce digits on testCliEdgeLengthM --- src/apps/filters/h3.c | 2 +- tests/cli/edgeLengthM.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/filters/h3.c b/src/apps/filters/h3.c index a3586b7e39..0fa10e6e59 100644 --- a/src/apps/filters/h3.c +++ b/src/apps/filters/h3.c @@ -2642,7 +2642,7 @@ SUBCOMMAND(edgeLengthM, if (err) { return err; } - printf("%.10lf\n", length); + printf("%.6lf\n", length); return E_SUCCESS; } diff --git a/tests/cli/edgeLengthM.txt b/tests/cli/edgeLengthM.txt index bcddc41be4..0dd238a9b2 100644 --- a/tests/cli/edgeLengthM.txt +++ b/tests/cli/edgeLengthM.txt @@ -1,4 +1,4 @@ add_h3_cli_test(testCliEdgeLengthM "edgeLengthM -c 115283473fffffff" - "10294.7360861995") + "10294.736086") add_h3_cli_test(testCliNotEdgeLengthM "edgeLengthM -c 85283473fffffff 2>&1" "Error 6: Directed edge argument was not valid") From 0235f15a94b73be85573d99bbf1e77e6789cdc02 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 14:18:44 -0700 Subject: [PATCH 39/91] vertexToLatLng benchmark --- src/apps/benchmarks/benchmarkCoreApi.c | 28 ++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c index 4c2ae904b4..dbf05363e9 100644 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -61,13 +61,18 @@ int main(void) { } } - // Pre-compute directed edges + // Pre-compute directed edges and vertices H3Index edges[N_POINTS * N_RESOLUTIONS]; + H3Index verts[N_POINTS * N_RESOLUTIONS]; int nEdges = 0; + int nVerts = 0; for (int c = 0; c < nCells; c++) { - H3Index out[6]; - H3_EXPORT(originToDirectedEdges)(cells[c], out); - edges[nEdges++] = out[0]; + H3Index edgeOut[6]; + H3_EXPORT(originToDirectedEdges)(cells[c], edgeOut); + edges[nEdges++] = edgeOut[0]; + H3Index vertOut[6]; + H3_EXPORT(cellToVertexes)(cells[c], vertOut); + verts[nVerts++] = vertOut[0]; } // Benchmark latLngToCell @@ -132,5 +137,20 @@ int main(void) { duration / totalCalls, totalCalls); } + // Benchmark vertexToLatLng + { + LatLng out; + int totalCalls = ITERATIONS * nVerts; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int v = 0; v < nVerts; v++) { + H3_EXPORT(vertexToLatLng)(verts[v], &out); + } + } + END_TIMER(duration); + printf("vertexToLatLng: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + return 0; } From d54ee33cedac8afeebc9795441203df35245ae6b Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 14:29:30 -0700 Subject: [PATCH 40/91] cover line in latLng.c --- src/apps/testapps/testLatLngInternal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/apps/testapps/testLatLngInternal.c b/src/apps/testapps/testLatLngInternal.c index 747504087a..03949b45d4 100644 --- a/src/apps/testapps/testLatLngInternal.c +++ b/src/apps/testapps/testLatLngInternal.c @@ -64,5 +64,7 @@ SUITE(latLngInternal) { t_assert(constrainLng(2 * M_PI) == 0, "lng 2pi"); t_assert(constrainLng(3 * M_PI) == M_PI, "lng 2pi"); t_assert(constrainLng(4 * M_PI) == 0, "lng 4pi"); + t_assert(constrainLng(-2 * M_PI) == 0, "lng -2pi"); + t_assert(constrainLng(-3 * M_PI) == -M_PI, "lng -3pi"); } } From 863d4c82c80346b59c137197643f1f0d22c733b4 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 16:36:58 -0700 Subject: [PATCH 41/91] remove temporary benchmarks and assignment tests --- CMakeLists.txt | 4 - bench.py | 153 -------------- src/apps/benchmarks/benchmarkCoreApi.c | 156 -------------- src/apps/benchmarks/dumpCoreApi.c | 120 ----------- stress.py | 275 ------------------------- 5 files changed, 708 deletions(-) delete mode 100644 bench.py delete mode 100644 src/apps/benchmarks/benchmarkCoreApi.c delete mode 100644 src/apps/benchmarks/dumpCoreApi.c delete mode 100644 stress.py diff --git a/CMakeLists.txt b/CMakeLists.txt index de95c90baa..5548a6eb01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,8 +324,6 @@ set(OTHER_SOURCE_FILES src/apps/benchmarks/benchmarkVertex.c src/apps/benchmarks/benchmarkIsValidCell.c src/apps/benchmarks/benchmarkH3Api.c - src/apps/benchmarks/benchmarkCoreApi.c - src/apps/benchmarks/dumpCoreApi.c src/apps/benchmarks/benchmarkArea.c) set(ALL_SOURCE_FILES @@ -666,8 +664,6 @@ if(BUILD_BENCHMARKS) endmacro() add_h3_benchmark(benchmarkH3Api src/apps/benchmarks/benchmarkH3Api.c) - add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c) - add_h3_benchmark(dumpCoreApi src/apps/benchmarks/dumpCoreApi.c) add_h3_benchmark(benchmarkGridDiskCells src/apps/benchmarks/benchmarkGridDiskCells.c) add_h3_benchmark(benchmarkGridPathCells diff --git a/bench.py b/bench.py deleted file mode 100644 index 8a51ba9d59..0000000000 --- a/bench.py +++ /dev/null @@ -1,153 +0,0 @@ -# /// script -# requires-python = ">=3.10" -# /// - -""" -Benchmark latLngToCell, cellToLatLng, cellToBoundary on two git refs. - -Checks out each ref, injects the benchmark C file if needed, builds, -runs N times, and reports the min. Restores the original branch on exit. - -Usage: - uv run bench.py [ref_a] [ref_b] - -Defaults to master vs vec3d-core. -""" - -import os -import re -import subprocess -import sys - -ROOT = os.path.dirname(os.path.abspath(__file__)) -BUILD = os.path.join(ROOT, "build") -BENCH_SRC = "src/apps/benchmarks/benchmarkCoreApi.c" -BENCH_BIN = os.path.join(BUILD, "bin/benchmarkCoreApi") -CMAKE_ENTRY = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" -N_RUNS = 10 - - -def git(*args): - return subprocess.run( - ["git", *args], capture_output=True, text=True, cwd=ROOT - ) - - -def current_ref(): - r = git("rev-parse", "--abbrev-ref", "HEAD") - branch = r.stdout.strip() - r = git("rev-parse", "--short", "HEAD") - return branch, r.stdout.strip() - - -def checkout(ref, bench_content): - """Switch to ref, injecting benchmark file and CMakeLists entry.""" - # Clean before switching - git("checkout", "--", ".") - src = os.path.join(ROOT, BENCH_SRC) - r = git("ls-files", BENCH_SRC) - if not r.stdout.strip() and os.path.exists(src): - os.remove(src) - - git("checkout", ref) - - # Inject benchmark file - os.makedirs(os.path.dirname(src), exist_ok=True) - with open(src, "w") as f: - f.write(bench_content) - - # Inject CMakeLists entry - cmake = os.path.join(ROOT, "CMakeLists.txt") - txt = open(cmake).read() - if "benchmarkCoreApi" not in txt: - txt = txt.replace( - "add_h3_benchmark(benchmarkH3Api", - CMAKE_ENTRY + "\n add_h3_benchmark(benchmarkH3Api", - ) - open(cmake, "w").write(txt) - - -def build(): - os.makedirs(BUILD, exist_ok=True) - r = subprocess.run( - "cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", - shell=True, capture_output=True, text=True, cwd=BUILD, - ) - if r.returncode != 0: - print(f" BUILD FAILED:\n{r.stderr[-300:]}") - return False - return True - - -def bench(n_runs): - """Run benchmark n_runs times, return {name: min_us}.""" - best = {} - for _ in range(n_runs): - r = subprocess.run([BENCH_BIN], capture_output=True, text=True) - for line in r.stdout.splitlines(): - m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) - if m: - name, us = m.group(1), float(m.group(2)) - if name not in best or us < best[name]: - best[name] = us - return best - - -def bench_ref(ref, bench_content): - """Checkout ref, build, benchmark. Returns {name: min_us} or None.""" - print(f"\n{'='*50}") - print(f"Benchmarking: {ref}") - print(f"{'='*50}") - - checkout(ref, bench_content) - branch, sha = current_ref() - print(f" branch: {branch} commit: {sha}") - - if not build(): - return None - - print(f" Running {N_RUNS} iterations...") - results = bench(N_RUNS) - for name, us in results.items(): - print(f" {name}: {us:.4f} us/call") - return results - - -def main(): - ref_a = sys.argv[1] if len(sys.argv) > 1 else "master" - ref_b = sys.argv[2] if len(sys.argv) > 2 else "vec3d-core" - - original, _ = current_ref() - src = os.path.join(ROOT, BENCH_SRC) - if not os.path.exists(src): - print(f"Error: {BENCH_SRC} not found.") - return - bench_content = open(src).read() - - results = {} - try: - for ref in [ref_a, ref_b]: - r = bench_ref(ref, bench_content) - if r: - results[ref] = r - finally: - checkout(original, bench_content) - print(f"\nRestored: {original}") - - if len(results) == 2: - print(f"\n{'='*50}") - print(f"Comparison (min of {N_RUNS} runs)") - print(f"{'='*50}") - print(f"{'Function':<20} {ref_a:>12} {ref_b:>12} {'Change':>10}") - print(f"{'-'*20} {'-'*12} {'-'*12} {'-'*10}") - for name in results[ref_a]: - a = results[ref_a][name] - b = results[ref_b].get(name) - if b is not None: - pct = (b - a) / a * 100 - sign = "+" if pct > 0 else "" - print(f"{name:<20} {a:>10.4f}us {b:>10.4f}us {sign}{pct:>8.1f}%") - - -if __name__ == "__main__": - main() diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c deleted file mode 100644 index dbf05363e9..0000000000 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2026 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 benchmarkCoreApi.c - * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary, - * directedEdgeToBoundary with diverse inputs. - * - * Uses START_TIMER/END_TIMER from benchmark.h for cross-platform timing, - * but reports per-call microseconds (not per-iteration) for use with - * bench.py. - */ - -#include - -#include "benchmark.h" -#include "h3api.h" - -#define N_POINTS 20 -#define N_RESOLUTIONS 4 -#define ITERATIONS 50000 - -// Points spread across the globe (hitting different icosahedron faces) -static const LatLng points[N_POINTS] = { - {0.659966917655, -2.1364398519396}, {0.8527087756, -0.0405865662}, - {0.6234025842, 2.0075945568}, {-0.5934119457, 2.5368879644}, - {0.4799655443, 0.6457718232}, {-0.4014257280, -0.7610418886}, - {0.9679776674, -1.7453292520}, {-1.2217304764, 0.0000000000}, - {1.2217304764, 0.0000000000}, {0.0000000000, 0.0000000000}, - {0.0000000000, 3.1415926536}, {0.7853981634, 1.5707963268}, - {-0.7853981634, -1.5707963268}, {0.3490658504, -1.2217304764}, - {-0.1745329252, 0.5235987756}, {1.0471975512, -0.5235987756}, - {-1.0471975512, 2.0943951024}, {0.2617993878, 1.8325957146}, - {-0.8726646260, -1.0471975512}, {0.5235987756, -2.6179938780}, -}; - -static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; - -int main(void) { - H3Index cells[N_POINTS * N_RESOLUTIONS]; - int nCells = 0; - - // Pre-compute cells for inverse benchmarks - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); - nCells++; - } - } - - // Pre-compute directed edges and vertices - H3Index edges[N_POINTS * N_RESOLUTIONS]; - H3Index verts[N_POINTS * N_RESOLUTIONS]; - int nEdges = 0; - int nVerts = 0; - for (int c = 0; c < nCells; c++) { - H3Index edgeOut[6]; - H3_EXPORT(originToDirectedEdges)(cells[c], edgeOut); - edges[nEdges++] = edgeOut[0]; - H3Index vertOut[6]; - H3_EXPORT(cellToVertexes)(cells[c], vertOut); - verts[nVerts++] = vertOut[0]; - } - - // Benchmark latLngToCell - { - H3Index h; - int totalCalls = ITERATIONS * N_POINTS * N_RESOLUTIONS; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); - } - } - } - END_TIMER(duration); - printf("latLngToCell: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark cellToLatLng - { - LatLng out; - int totalCalls = ITERATIONS * nCells; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToLatLng)(cells[c], &out); - } - } - END_TIMER(duration); - printf("cellToLatLng: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark cellToBoundary - { - CellBoundary cb; - int totalCalls = ITERATIONS * nCells; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToBoundary)(cells[c], &cb); - } - } - END_TIMER(duration); - printf("cellToBoundary: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark directedEdgeToBoundary - { - CellBoundary cb; - int totalCalls = ITERATIONS * nEdges; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int e = 0; e < nEdges; e++) { - H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); - } - } - END_TIMER(duration); - printf("directedEdgeToBoundary: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark vertexToLatLng - { - LatLng out; - int totalCalls = ITERATIONS * nVerts; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int v = 0; v < nVerts; v++) { - H3_EXPORT(vertexToLatLng)(verts[v], &out); - } - } - END_TIMER(duration); - printf("vertexToLatLng: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - return 0; -} diff --git a/src/apps/benchmarks/dumpCoreApi.c b/src/apps/benchmarks/dumpCoreApi.c deleted file mode 100644 index 390fd3f169..0000000000 --- a/src/apps/benchmarks/dumpCoreApi.c +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2026 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 dumpCoreApi.c - * @brief Dump latLngToCell, cellToLatLng, cellToBoundary results - * for cross-branch correctness comparison. - */ - -#include -#include -#include -#include - -#include "h3api.h" -#include "utility.h" - -#define N_RANDOM 10000 -#define SEED 12345 -#define MAX_EXHAUST_RES 4 -#define VERTEX_TEST_RES 3 - -static const int testResolutions[] = {0, 1, 2, 3, 4, 7, 10, 15}; -#define N_RES (sizeof(testResolutions) / sizeof(testResolutions[0])) - -static double rand01(void) { return (double)rand() / (double)RAND_MAX; } - -static void dumpLatLngToCell(double lat, double lng) { - LatLng g = {lat, lng}; - for (int r = 0; r < (int)N_RES; r++) { - H3Index h; - H3_EXPORT(latLngToCell)(&g, testResolutions[r], &h); - printf("%.17g %.17g %d %" PRIx64 "\n", lat, lng, testResolutions[r], h); - } -} - -/** Spherical midpoint of two LatLng points. */ -static void midpoint(const LatLng *a, const LatLng *b, LatLng *out) { - double c1 = cos(a->lat), c2 = cos(b->lat); - double x = c1 * cos(a->lng) + c2 * cos(b->lng); - double y = c1 * sin(a->lng) + c2 * sin(b->lng); - double z = sin(a->lat) + sin(b->lat); - double norm = sqrt(x * x + y * y + z * z); - out->lat = asin(z / norm); - out->lng = atan2(y, x); -} - -static void dumpVerticesAndEdges(H3Index h) { - CellBoundary cb; - H3_EXPORT(cellToBoundary)(h, &cb); - for (int i = 0; i < cb.numVerts; i++) { - // Vertex point - dumpLatLngToCell(cb.verts[i].lat, cb.verts[i].lng); - // Edge midpoint (between this vertex and the next) - int j = (i + 1) % cb.numVerts; - LatLng mid; - midpoint(&cb.verts[i], &cb.verts[j], &mid); - dumpLatLngToCell(mid.lat, mid.lng); - } -} - -static void dumpCellToLatLng(H3Index h) { - LatLng g; - H3_EXPORT(cellToLatLng)(h, &g); - printf("%" PRIx64 " %.17g %.17g\n", h, g.lat, g.lng); -} - -static void dumpCellToBoundary(H3Index h) { - CellBoundary cb; - H3_EXPORT(cellToBoundary)(h, &cb); - printf("%" PRIx64 " %d", h, cb.numVerts); - for (int i = 0; i < cb.numVerts; i++) { - printf(" %.17g %.17g", cb.verts[i].lat, cb.verts[i].lng); - } - printf("\n"); -} - -int main(void) { - // Section 1: latLngToCell with random points - printf("#section latLngToCell\n"); - srand(SEED); - for (int i = 0; i < N_RANDOM; i++) { - double lat = asin(2.0 * rand01() - 1.0); - double lng = (2.0 * rand01() - 1.0) * M_PI; - dumpLatLngToCell(lat, lng); - } - - // Section 2: latLngToCell at cell vertices and edge midpoints - printf("#section latLngToCellVertices\n"); - for (int res = 0; res <= VERTEX_TEST_RES; res++) { - iterateAllIndexesAtRes(res, dumpVerticesAndEdges); - } - - // Section 3: cellToLatLng exhaustive - printf("#section cellToLatLng\n"); - for (int res = 0; res <= MAX_EXHAUST_RES; res++) { - iterateAllIndexesAtRes(res, dumpCellToLatLng); - } - - // Section 4: cellToBoundary exhaustive - printf("#section cellToBoundary\n"); - for (int res = 0; res <= MAX_EXHAUST_RES; res++) { - iterateAllIndexesAtRes(res, dumpCellToBoundary); - } - - return 0; -} diff --git a/stress.py b/stress.py deleted file mode 100644 index c9290a7b92..0000000000 --- a/stress.py +++ /dev/null @@ -1,275 +0,0 @@ -# /// script -# requires-python = ">=3.10" -# /// - -""" -Run dumpCoreApi on two git refs and compare results. - -Usage: edit BRANCH_A, BRANCH_B below, then run with: - uv run stress.py -""" - -import subprocess -import os -import math - -BRANCH_A = "master" -BRANCH_B = "vec3d-core" -BUILD_DIR = "build" -DUMP_BIN = "bin/dumpCoreApi" - -ROOT = os.path.dirname(os.path.abspath(__file__)) -DUMP_SRC_REL = "src/apps/benchmarks/dumpCoreApi.c" -CMAKE_LINE = " add_h3_benchmark(dumpCoreApi src/apps/benchmarks/dumpCoreApi.c)" - - -def run(cmd): - return subprocess.run(cmd, capture_output=True, text=True, cwd=ROOT) - - -def prepare_branch(dump_content): - """Write dump file and CMakeLists entry onto current checkout.""" - dump_path = os.path.join(ROOT, DUMP_SRC_REL) - cmake_path = os.path.join(ROOT, "CMakeLists.txt") - - os.makedirs(os.path.dirname(dump_path), exist_ok=True) - with open(dump_path, "w") as f: - f.write(dump_content) - - with open(cmake_path) as f: - cmake = f.read() - if "dumpCoreApi" not in cmake: - cmake = cmake.replace( - "add_h3_benchmark(benchmarkH3Api", - CMAKE_LINE + "\n add_h3_benchmark(benchmarkH3Api" - ) - with open(cmake_path, "w") as f: - f.write(cmake) - - -def restore_branch(): - """Undo modifications to tracked files, remove untracked dump file.""" - run(["git", "checkout", "--", "."]) - dump_path = os.path.join(ROOT, DUMP_SRC_REL) - # Remove if untracked on this branch - r = run(["git", "ls-files", DUMP_SRC_REL]) - if not r.stdout.strip() and os.path.exists(dump_path): - os.remove(dump_path) - - -def switch_to(ref): - """Clean up current branch and switch to ref.""" - restore_branch() - r = run(["git", "checkout", ref]) - if r.returncode != 0: - print(f" checkout failed: {r.stderr.strip()}") - return False - return True - - -def get_branch_info(): - r1 = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - r2 = run(["git", "rev-parse", "--short", "HEAD"]) - return r1.stdout.strip(), r2.stdout.strip() - - -def build_and_run(ref, dump_content): - print(f"\n{'='*50}") - print(f"Running: {ref}") - print(f"{'='*50}") - - if not switch_to(ref): - return None - - branch, sha = get_branch_info() - print(f" branch: {branch} commit: {sha}") - - prepare_branch(dump_content) - - r = subprocess.run( - f"cd {BUILD_DIR} && cmake -DCMAKE_BUILD_TYPE=Release .. && make dumpCoreApi", - shell=True, capture_output=True, text=True, cwd=ROOT - ) - if r.returncode != 0: - print(f"Build failed: {r.stderr[-300:]}") - return None - - bin_path = os.path.join(ROOT, BUILD_DIR, DUMP_BIN) - r = subprocess.run([bin_path], capture_output=True, text=True) - print(f" {len(r.stdout.splitlines())} lines of output") - return r.stdout - - -def parse_sections(text): - sections = {} - current = None - for line in text.splitlines(): - if line.startswith("#section "): - current = line.split(" ", 1)[1] - sections[current] = [] - elif current is not None: - sections[current].append(line) - return sections - - -def compare_latLngToCell(lines_a, lines_b, label="latLngToCell"): - print(f"\n--- {label} ---") - if len(lines_a) != len(lines_b): - print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") - return - - mismatches = 0 - for la, lb in zip(lines_a, lines_b): - pa = la.split() - pb = lb.split() - if pa[3] != pb[3]: - mismatches += 1 - if mismatches <= 20: - print(f" MISMATCH: lat={pa[0]} lng={pa[1]} res={pa[2]}") - print(f" {BRANCH_A}: {pa[3]}") - print(f" {BRANCH_B}: {pb[3]}") - - print(f" Total: {len(lines_a)}") - print(f" Mismatches: {mismatches}") - if mismatches > 20: - print(f" (showing first 20 of {mismatches})") - - -def compare_cellToLatLng(lines_a, lines_b): - print(f"\n--- cellToLatLng ---") - if len(lines_a) != len(lines_b): - print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") - return - - max_lat_diff = 0.0 - max_lng_diff = 0.0 - max_lat_cell = "" - max_lng_cell = "" - n_exact = 0 - - for la, lb in zip(lines_a, lines_b): - pa = la.split() - pb = lb.split() - lat_a, lng_a = float(pa[1]), float(pa[2]) - lat_b, lng_b = float(pb[1]), float(pb[2]) - - dlat = abs(lat_a - lat_b) - dlng = abs(lng_a - lng_b) - if dlng > math.pi: - dlng = 2 * math.pi - dlng - - if dlat == 0.0 and dlng == 0.0: - n_exact += 1 - if dlat > max_lat_diff: - max_lat_diff = dlat - max_lat_cell = pa[0] - if dlng > max_lng_diff: - max_lng_diff = dlng - max_lng_cell = pa[0] - - total = len(lines_a) - print(f" Total cells: {total}") - print(f" Exact matches: {n_exact} ({100*n_exact/total:.1f}%)") - print(f" Max lat diff: {max_lat_diff:.3e} rad (cell {max_lat_cell})") - print(f" Max lng diff: {max_lng_diff:.3e} rad (cell {max_lng_cell})") - if max_lat_diff > 0: - print(f" Max lat diff: ~{max_lat_diff * 6.371e6:.6f} meters") - if max_lng_diff > 0: - print(f" Max lng diff: ~{max_lng_diff * 6.371e6:.6f} meters") - - -def compare_cellToBoundary(lines_a, lines_b): - print(f"\n--- cellToBoundary ---") - if len(lines_a) != len(lines_b): - print(f" LINE COUNT MISMATCH: {len(lines_a)} vs {len(lines_b)}") - return - - max_vert_diff = 0.0 - max_vert_cell = "" - max_vert_idx = -1 - vert_count_mismatches = 0 - n_exact = 0 - - for la, lb in zip(lines_a, lines_b): - pa = la.split() - pb = lb.split() - nverts_a, nverts_b = int(pa[1]), int(pb[1]) - - if nverts_a != nverts_b: - vert_count_mismatches += 1 - continue - - cell_exact = True - for v in range(nverts_a): - lat_a = float(pa[2 + v * 2]) - lng_a = float(pa[3 + v * 2]) - lat_b = float(pb[2 + v * 2]) - lng_b = float(pb[3 + v * 2]) - - dlat = abs(lat_a - lat_b) - dlng = abs(lng_a - lng_b) - if dlng > math.pi: - dlng = 2 * math.pi - dlng - - diff = max(dlat, dlng) - if diff > 0: - cell_exact = False - if diff > max_vert_diff: - max_vert_diff = diff - max_vert_cell = pa[0] - max_vert_idx = v - - if cell_exact: - n_exact += 1 - - total = len(lines_a) - print(f" Total cells: {total}") - print(f" Exact matches: {n_exact} ({100*n_exact/total:.1f}%)") - print(f" Vertex count mismatches: {vert_count_mismatches}") - print(f" Max vertex diff: {max_vert_diff:.3e} rad (cell {max_vert_cell}, vertex {max_vert_idx})") - if max_vert_diff > 0: - print(f" Max vertex diff: ~{max_vert_diff * 6.371e6:.6f} meters") - - -def main(): - r = run(["git", "rev-parse", "--abbrev-ref", "HEAD"]) - original_branch = r.stdout.strip() - - # Read dump file content before any branch switching - dump_path = os.path.join(ROOT, DUMP_SRC_REL) - if not os.path.exists(dump_path): - print(f"Error: {DUMP_SRC_REL} not found.") - return - with open(dump_path) as f: - dump_content = f.read() - - outputs = {} - try: - for ref in [BRANCH_A, BRANCH_B]: - output = build_and_run(ref, dump_content) - if output is None: - return - outputs[ref] = output - finally: - switch_to(original_branch) - print(f"\nRestored branch: {original_branch}") - - if len(outputs) != 2: - return - - sec_a = parse_sections(outputs[BRANCH_A]) - sec_b = parse_sections(outputs[BRANCH_B]) - - print(f"\n{'='*50}") - print(f"Comparison: {BRANCH_A} vs {BRANCH_B}") - print(f"{'='*50}") - - compare_latLngToCell(sec_a["latLngToCell"], sec_b["latLngToCell"]) - compare_latLngToCell(sec_a["latLngToCellVertices"], sec_b["latLngToCellVertices"], - label="latLngToCell (vertices & edges)") - compare_cellToLatLng(sec_a["cellToLatLng"], sec_b["cellToLatLng"]) - compare_cellToBoundary(sec_a["cellToBoundary"], sec_b["cellToBoundary"]) - - -if __name__ == "__main__": - main() From 9b20e308d4218453e48eeb209eb50b7dc942f17f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:07:30 -0700 Subject: [PATCH 42/91] _faceIjkToCell back to _faceIjkToH3 --- .../miscapps/generatePentagonDirectionFaces.c | 2 +- src/apps/testapps/testH3IndexInternal.c | 18 ++++++++--------- src/h3lib/include/faceijk.h | 4 ++-- src/h3lib/include/h3Index.h | 6 +++--- src/h3lib/lib/directedEdge.c | 4 ++-- src/h3lib/lib/faceijk.c | 4 ++-- src/h3lib/lib/h3Index.c | 20 +++++++++---------- src/h3lib/lib/localij.c | 2 +- src/h3lib/lib/vertex.c | 6 +++--- 9 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/apps/miscapps/generatePentagonDirectionFaces.c b/src/apps/miscapps/generatePentagonDirectionFaces.c index 185182ac9a..789a6c5052 100644 --- a/src/apps/miscapps/generatePentagonDirectionFaces.c +++ b/src/apps/miscapps/generatePentagonDirectionFaces.c @@ -46,7 +46,7 @@ static void generate(void) { int r = 0; H3Index neighbor; h3NeighborRotations(pentagon, dir, &r, &neighbor); - _cellToFaceIjk(neighbor, &fijk); + _h3ToFaceIjk(neighbor, &fijk); if (dir > J_AXES_DIGIT) printf(", "); printf("%d", fijk.face); diff --git a/src/apps/testapps/testH3IndexInternal.c b/src/apps/testapps/testH3IndexInternal.c index 6703912a92..e88bfa1975 100644 --- a/src/apps/testapps/testH3IndexInternal.c +++ b/src/apps/testapps/testH3IndexInternal.c @@ -31,24 +31,24 @@ SUITE(h3IndexInternal) { TEST(faceIjkToH3ExtremeCoordinates) { FaceIJK fijk0I = {0, {3, 0, 0}}; - t_assert(_faceIjkToCell(&fijk0I, 0) == 0, "i out of bounds at res 0"); + t_assert(_faceIjkToH3(&fijk0I, 0) == 0, "i out of bounds at res 0"); FaceIJK fijk0J = {1, {0, 4, 0}}; - t_assert(_faceIjkToCell(&fijk0J, 0) == 0, "j out of bounds at res 0"); + t_assert(_faceIjkToH3(&fijk0J, 0) == 0, "j out of bounds at res 0"); FaceIJK fijk0K = {2, {2, 0, 5}}; - t_assert(_faceIjkToCell(&fijk0K, 0) == 0, "k out of bounds at res 0"); + t_assert(_faceIjkToH3(&fijk0K, 0) == 0, "k out of bounds at res 0"); FaceIJK fijk1I = {3, {6, 0, 0}}; - t_assert(_faceIjkToCell(&fijk1I, 1) == 0, "i out of bounds at res 1"); + t_assert(_faceIjkToH3(&fijk1I, 1) == 0, "i out of bounds at res 1"); FaceIJK fijk1J = {4, {0, 7, 1}}; - t_assert(_faceIjkToCell(&fijk1J, 1) == 0, "j out of bounds at res 1"); + t_assert(_faceIjkToH3(&fijk1J, 1) == 0, "j out of bounds at res 1"); FaceIJK fijk1K = {5, {2, 0, 8}}; - t_assert(_faceIjkToCell(&fijk1K, 1) == 0, "k out of bounds at res 1"); + t_assert(_faceIjkToH3(&fijk1K, 1) == 0, "k out of bounds at res 1"); FaceIJK fijk2I = {6, {18, 0, 0}}; - t_assert(_faceIjkToCell(&fijk2I, 2) == 0, "i out of bounds at res 2"); + t_assert(_faceIjkToH3(&fijk2I, 2) == 0, "i out of bounds at res 2"); FaceIJK fijk2J = {7, {0, 19, 1}}; - t_assert(_faceIjkToCell(&fijk2J, 2) == 0, "j out of bounds at res 2"); + t_assert(_faceIjkToH3(&fijk2J, 2) == 0, "j out of bounds at res 2"); FaceIJK fijk2K = {8, {2, 0, 20}}; - t_assert(_faceIjkToCell(&fijk2K, 2) == 0, "k out of bounds at res 2"); + t_assert(_faceIjkToH3(&fijk2K, 2) == 0, "k out of bounds at res 2"); } } diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index c78319a084..3e338cd557 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -75,8 +75,8 @@ typedef enum { void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h); Vec3 _faceIjkToVec3(const FaceIJK *h, int res); -void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, - CellBoundary *g); +void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index 8faf30228b..50222a72c1 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -169,9 +169,9 @@ int isResolutionClassIII(int r); // Internal functions -int _cellToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); -H3Error _cellToFaceIjk(H3Index h, FaceIJK *fijk); -H3Index _faceIjkToCell(const FaceIJK *fijk, int res); +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk); +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk); +H3Index _faceIjkToH3(const FaceIJK *fijk, int res); Direction _h3LeadingNonZeroDigit(H3Index h); H3Index _h3RotatePent60ccw(H3Index h); H3Index _h3RotatePent60cw(H3Index h); diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c index fadd3265d4..202b437e60 100644 --- a/src/h3lib/lib/directedEdge.c +++ b/src/h3lib/lib/directedEdge.c @@ -278,7 +278,7 @@ H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { // resulting edge boundary may have an additional distortion vertex if it // crosses an edge of the icosahedron. FaceIJK fijk; - H3Error fijkResult = _cellToFaceIjk(origin, &fijk); + H3Error fijkResult = _h3ToFaceIjk(origin, &fijk); if (NEVER(fijkResult)) { return fijkResult; } @@ -288,7 +288,7 @@ H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { if (isPent) { _faceIjkPentToCellBoundary(&fijk, res, startVertex, 2, cb); } else { - _faceIjkToCellBoundary(&fijk, res, startVertex, 2, cb); + _faceIjkToH3Boundary(&fijk, res, startVertex, 2, cb); } return E_SUCCESS; } diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index e1919ed62e..d18d7859e5 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -723,8 +723,8 @@ void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { * @param length The number of topological vertexes to return. * @param g Output: the spherical coordinates of the cell boundary. */ -void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, - CellBoundary *g) { +void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g) { int adjRes = res; FaceIJK centerIJK = *h; FaceIJK fijkVerts[NUM_HEX_VERTS]; diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 8121449753..33c51dd9ba 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -939,7 +939,7 @@ H3Index _h3Rotate60cw(H3Index h) { * @param res The cell resolution. * @return The encoded H3Index (or H3_NULL on failure). */ -H3Index _faceIjkToCell(const FaceIJK *fijk, int res) { +H3Index _faceIjkToH3(const FaceIJK *fijk, int res) { // initialize the index H3Index h = H3_INIT; H3_SET_MODE(h, H3_CELL_MODE); @@ -1069,7 +1069,7 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { FaceIJK fijk; _vec3ToFaceIjk(*v, res, &fijk); - *out = _faceIjkToCell(&fijk, res); + *out = _faceIjkToH3(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; } else { @@ -1084,7 +1084,7 @@ H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { * and normalized base cell coordinates. * @return Returns 1 if the possibility of overage exists, otherwise 0. */ -int _cellToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { +int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { CoordIJK *ijk = &fijk->coord; int res = H3_GET_RESOLUTION(h); @@ -1115,7 +1115,7 @@ int _cellToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { * @param h The H3Index. * @param fijk The corresponding FaceIJK address. */ -H3Error _cellToFaceIjk(H3Index h, FaceIJK *fijk) { +H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { int baseCell = H3_GET_BASE_CELL(h); if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { // Base cells less than zero can not be represented in an index @@ -1131,7 +1131,7 @@ H3Error _cellToFaceIjk(H3Index h, FaceIJK *fijk) { // start with the "home" face and ijk+ coordinates for the base cell of c *fijk = baseCellData[baseCell].homeFijk; - if (!_cellToFaceIjkWithInitializedFijk(h, fijk)) + if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) return E_SUCCESS; // no overage is possible; h lies on this face // if we're here we have the potential for an "overage"; i.e., it is @@ -1191,7 +1191,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { */ H3Error cellToVec3(H3Index h3, Vec3 *v) { FaceIJK fijk; - H3Error e = _cellToFaceIjk(h3, &fijk); + H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { return e; } @@ -1207,7 +1207,7 @@ H3Error cellToVec3(H3Index h3, Vec3 *v) { */ H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { FaceIJK fijk; - H3Error e = _cellToFaceIjk(h3, &fijk); + H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { return e; } @@ -1215,8 +1215,8 @@ H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { _faceIjkPentToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_PENT_VERTS, cb); } else { - _faceIjkToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, - cb); + _faceIjkToH3Boundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, + cb); } return E_SUCCESS; } @@ -1259,7 +1259,7 @@ H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out) { // convert to FaceIJK FaceIJK fijk; - H3Error err = _cellToFaceIjk(h3, &fijk); + H3Error err = _h3ToFaceIjk(h3, &fijk); if (err) { return err; } diff --git a/src/h3lib/lib/localij.c b/src/h3lib/lib/localij.c index 43b7a7e7e0..7d71a7df0d 100644 --- a/src/h3lib/lib/localij.c +++ b/src/h3lib/lib/localij.c @@ -188,7 +188,7 @@ H3Error cellToLocalIjk(H3Index origin, H3Index h3, CoordIJK *out) { } } // Face is unused. This produces coordinates in base cell coordinate space. - _cellToFaceIjkWithInitializedFijk(h3, &indexFijk); + _h3ToFaceIjkWithInitializedFijk(h3, &indexFijk); if (dir != CENTER_DIGIT) { assert(baseCell != originBaseCell); diff --git a/src/h3lib/lib/vertex.c b/src/h3lib/lib/vertex.c index f433d4bf6e..9f90a83245 100644 --- a/src/h3lib/lib/vertex.c +++ b/src/h3lib/lib/vertex.c @@ -53,7 +53,7 @@ static const PentagonDirectionFaces pentagonDirectionFaces[NUM_PENTAGONS] = { static H3Error vertexRotations(H3Index cell, int *out) { // Get the face and other info for the origin FaceIJK fijk; - H3Error err = _cellToFaceIjk(cell, &fijk); + H3Error err = _h3ToFaceIjk(cell, &fijk); if (err) { return err; } @@ -329,7 +329,7 @@ H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { // Get the single vertex from the boundary CellBoundary gb; FaceIJK fijk; - H3Error fijkError = _cellToFaceIjk(owner, &fijk); + H3Error fijkError = _h3ToFaceIjk(owner, &fijk); if (fijkError) { return fijkError; } @@ -338,7 +338,7 @@ H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { if (H3_EXPORT(isPentagon)(owner)) { _faceIjkPentToCellBoundary(&fijk, res, vertexNum, 1, &gb); } else { - _faceIjkToCellBoundary(&fijk, res, vertexNum, 1, &gb); + _faceIjkToH3Boundary(&fijk, res, vertexNum, 1, &gb); } // Copy from boundary to output coord From 3229a5c75738b5961b7c3f4947a1ffec862b89c7 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:16:39 -0700 Subject: [PATCH 43/91] restore Vec3d --- CMakeLists.txt | 2 +- src/apps/miscapps/generateFaceCenterPoint.c | 2 +- src/apps/testapps/testVec3.c | 2 +- src/apps/testapps/testVec3Internal.c | 2 +- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/{vec3.h => vec3d.h} | 4 ++-- src/h3lib/lib/faceijk.c | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) rename src/h3lib/include/{vec3.h => vec3d.h} (98%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5548a6eb01..da7f09934c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,7 +158,7 @@ set(LIB_SOURCE_FILES src/h3lib/include/directedEdge.h src/h3lib/include/latLng.h src/h3lib/include/vec2.h - src/h3lib/include/vec3.h + src/h3lib/include/vec3d.h src/h3lib/include/linkedGeo.h src/h3lib/include/localij.h src/h3lib/include/baseCells.h diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index 0f28579833..77e2f74d39 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -22,7 +22,7 @@ #include #include "faceijk.h" -#include "vec3.h" +#include "vec3d.h" /** @brief icosahedron face centers in lat/lng radians. Copied from faceijk.c. */ diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index 1241ac43ab..da763ee47b 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -23,7 +23,7 @@ #include "constants.h" #include "h3Index.h" #include "test.h" -#include "vec3.h" +#include "vec3d.h" SUITE(Vec3) { TEST(dotProduct) { diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3Internal.c index a5b1730ea1..ddb73a9ad3 100644 --- a/src/apps/testapps/testVec3Internal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -17,7 +17,7 @@ #include #include "test.h" -#include "vec3.h" +#include "vec3d.h" SUITE(Vec3Internal) { TEST(latLngToVec3) { diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 3e338cd557..6a4b4c1b69 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -27,7 +27,7 @@ #include "coordijk.h" #include "latLng.h" #include "vec2.h" -#include "vec3.h" +#include "vec3d.h" /** @struct FaceIJK * @brief Face number and ijk coordinates on that face-centered coordinate diff --git a/src/h3lib/include/vec3.h b/src/h3lib/include/vec3d.h similarity index 98% rename from src/h3lib/include/vec3.h rename to src/h3lib/include/vec3d.h index e3ac2723e6..f6a950c9cf 100644 --- a/src/h3lib/include/vec3.h +++ b/src/h3lib/include/vec3d.h @@ -20,8 +20,8 @@ * can inline these without requiring LTO. */ -#ifndef VEC3_H -#define VEC3_H +#ifndef VEC3D_H +#define VEC3D_H #include diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index d18d7859e5..7d4146a833 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -30,7 +30,7 @@ #include "coordijk.h" #include "h3Index.h" #include "latLng.h" -#include "vec3.h" +#include "vec3d.h" /** square root of 7 and inverse square root of 7 */ #define M_SQRT7 2.6457513110645905905016157536392604257102 From 739e2b15c5b23858932531e85ca5c2fa6e3798c7 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:22:02 -0700 Subject: [PATCH 44/91] missed a few --- src/apps/miscapps/generateFaceCenterPoint.c | 4 +-- src/apps/testapps/testVec3.c | 32 ++++++++--------- src/apps/testapps/testVec3Internal.c | 10 +++--- src/h3lib/include/faceijk.h | 4 +-- src/h3lib/include/h3Index.h | 4 +-- src/h3lib/include/vec3d.h | 32 ++++++++--------- src/h3lib/lib/faceijk.c | 38 ++++++++++----------- src/h3lib/lib/h3Index.c | 8 ++--- 8 files changed, 66 insertions(+), 66 deletions(-) diff --git a/src/apps/miscapps/generateFaceCenterPoint.c b/src/apps/miscapps/generateFaceCenterPoint.c index 77e2f74d39..73fa9e646e 100644 --- a/src/apps/miscapps/generateFaceCenterPoint.c +++ b/src/apps/miscapps/generateFaceCenterPoint.c @@ -53,10 +53,10 @@ const LatLng faceCenterGeoCopy[NUM_ICOSA_FACES] = { * Generates and prints the faceCenterPoint table. */ static void generate(void) { - printf("static const Vec3 faceCenterPoint[NUM_ICOSA_FACES] = {\n"); + printf("static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = {\n"); for (int i = 0; i < NUM_ICOSA_FACES; i++) { LatLng centerCoords = faceCenterGeoCopy[i]; - Vec3 centerPoint = latLngToVec3(centerCoords); + Vec3d centerPoint = latLngToVec3(centerCoords); printf(" {%.16f, %.16f, %.16f}, // face %2d\n", centerPoint.x, centerPoint.y, centerPoint.z, i); } diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index da763ee47b..cd38bca4a5 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -15,7 +15,7 @@ */ /** @file testVec3.c - * @brief Tests the Vec3 helpers used by the geodesic polyfill path. + * @brief Tests the Vec3d helpers used by the geodesic polyfill path. */ #include @@ -25,17 +25,17 @@ #include "test.h" #include "vec3d.h" -SUITE(Vec3) { +SUITE(Vec3d) { TEST(dotProduct) { - Vec3 a = {.x = 1.0, .y = 0.0, .z = 0.0}; - Vec3 b = {.x = -1.0, .y = 0.0, .z = 0.0}; + Vec3d a = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3d b = {.x = -1.0, .y = 0.0, .z = 0.0}; t_assert(vec3Dot(a, b) == -1.0, "dot product matches expected value"); } TEST(crossProductOrthogonality) { - Vec3 i = {.x = 1.0, .y = 0.0, .z = 0.0}; - Vec3 j = {.x = 0.0, .y = 1.0, .z = 0.0}; - Vec3 k = vec3Cross(i, j); + Vec3d i = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3d j = {.x = 0.0, .y = 1.0, .z = 0.0}; + Vec3d k = vec3Cross(i, j); t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); t_assert(fabs(k.z - 1.0) < EPSILON, "z component one"); @@ -44,7 +44,7 @@ SUITE(Vec3) { } TEST(normalizeAndMagnitude) { - Vec3 v = {.x = 3.0, .y = -4.0, .z = 12.0}; + Vec3d v = {.x = 3.0, .y = -4.0, .z = 12.0}; double magSq = vec3NormSq(v); t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); t_assert(fabs(vec3Norm(v) - 13.0) < EPSILON, "magnitude matches"); @@ -52,28 +52,28 @@ SUITE(Vec3) { vec3Normalize(&v); t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, "normalized vector is unit"); - Vec3 zero = {.x = 0.0, .y = 0.0, .z = 0.0}; + Vec3d zero = {.x = 0.0, .y = 0.0, .z = 0.0}; vec3Normalize(&zero); t_assert(zero.x == 0.0 && zero.y == 0.0 && zero.z == 0.0, "zero vector remains unchanged when normalizing"); } TEST(distance) { - Vec3 a = {.x = 0.0, .y = 0.0, .z = 0.0}; - Vec3 b = {.x = 1.0, .y = 2.0, .z = 2.0}; + Vec3d a = {.x = 0.0, .y = 0.0, .z = 0.0}; + Vec3d b = {.x = 1.0, .y = 2.0, .z = 2.0}; t_assert(fabs(vec3DistSq(a, b) - 9.0) < EPSILON, "distance squared matches"); } TEST(latLngToVec3_unitSphere) { LatLng geo = {.lat = 0.5, .lng = -1.3}; - Vec3 v = latLngToVec3(geo); + Vec3d v = latLngToVec3(geo); t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, "converted vector lives on the unit sphere"); } TEST(vec3ToCell_invalidRes) { - Vec3 v = {.x = 1.0, .y = 0.0, .z = 0.0}; + Vec3d v = {.x = 1.0, .y = 0.0, .z = 0.0}; H3Index out; t_assert(vec3ToCell(&v, -1, &out) == E_RES_DOMAIN, "negative resolution is rejected"); @@ -83,12 +83,12 @@ SUITE(Vec3) { TEST(vec3ToCell_nonFinite) { H3Index out; - Vec3 nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; + Vec3d nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; t_assert(vec3ToCell(&nan_x, 0, &out) == E_DOMAIN, "NaN x is rejected"); - Vec3 inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; + Vec3d inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; t_assert(vec3ToCell(&inf_y, 0, &out) == E_DOMAIN, "infinite y is rejected"); - Vec3 inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; + Vec3d inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; t_assert(vec3ToCell(&inf_z, 0, &out) == E_DOMAIN, "infinite z is rejected"); } diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3Internal.c index ddb73a9ad3..5470aa85b5 100644 --- a/src/apps/testapps/testVec3Internal.c +++ b/src/apps/testapps/testVec3Internal.c @@ -19,22 +19,22 @@ #include "test.h" #include "vec3d.h" -SUITE(Vec3Internal) { +SUITE(Vec3dInternal) { TEST(latLngToVec3) { - Vec3 origin = {0}; + Vec3d origin = {0}; LatLng c1 = {0, 0}; - Vec3 p1 = latLngToVec3(c1); + Vec3d p1 = latLngToVec3(c1); t_assert(fabs(vec3DistSq(origin, p1) - 1) < EPSILON_RAD, "Geo point is on the unit sphere"); LatLng c2 = {M_PI_2, 0}; - Vec3 p2 = latLngToVec3(c2); + Vec3d p2 = latLngToVec3(c2); t_assert(fabs(vec3DistSq(p1, p2) - 2) < EPSILON_RAD, "Geo point is on another axis"); LatLng c3 = {M_PI, 0}; - Vec3 p3 = latLngToVec3(c3); + Vec3d p3 = latLngToVec3(c3); t_assert(fabs(vec3DistSq(p1, p3) - 4) < EPSILON_RAD, "Geo point is the other side of the sphere"); } diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 6a4b4c1b69..21782f0383 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -73,8 +73,8 @@ typedef enum { // Internal functions -void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h); -Vec3 _faceIjkToVec3(const FaceIJK *h, int res); +void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h); +Vec3d _faceIjkToVec3(const FaceIJK *h, int res); void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, diff --git a/src/h3lib/include/h3Index.h b/src/h3lib/include/h3Index.h index 50222a72c1..a90574e1f8 100644 --- a/src/h3lib/include/h3Index.h +++ b/src/h3lib/include/h3Index.h @@ -179,7 +179,7 @@ H3Index _h3Rotate60ccw(H3Index h); H3Index _h3Rotate60cw(H3Index h); DECLSPEC H3Index _zeroIndexDigits(H3Index h, int start, int end); -H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out); -H3Error cellToVec3(H3Index h3, Vec3 *v); +H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out); +H3Error cellToVec3(H3Index h3, Vec3d *v); #endif diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index f6a950c9cf..c4673cd0e0 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -28,7 +28,7 @@ #include "h3api.h" #include "latLng.h" -/** @struct Vec3 +/** @struct Vec3d * @brief 3D floating point structure * * For geodesic calulations represents a point on the surface of the Earth @@ -38,50 +38,50 @@ typedef struct { double x; /// towards 0deg lat, 0deg lon double y; /// towards 0deg lat, 90deg lon double z; /// towards north pole -} Vec3; +} Vec3d; -/** Convert latitude and longitude to a unit Vec3 on the sphere. */ -static inline Vec3 latLngToVec3(LatLng geo) { +/** Convert latitude and longitude to a unit Vec3d on the sphere. */ +static inline Vec3d latLngToVec3(LatLng geo) { double r = cos(geo.lat); - return (Vec3){ + return (Vec3d){ cos(geo.lng) * r, sin(geo.lng) * r, sin(geo.lat), }; } -static inline LatLng vec3ToLatLng(Vec3 v) { +static inline LatLng vec3ToLatLng(Vec3d v) { return (LatLng){ asin(v.z), atan2(v.y, v.x), }; } -static inline Vec3 vec3LinComb(double s1, Vec3 a, double s2, Vec3 b) { - return (Vec3){ +static inline Vec3d vec3LinComb(double s1, Vec3d a, double s2, Vec3d b) { + return (Vec3d){ s1 * a.x + s2 * b.x, s1 * a.y + s2 * b.y, s1 * a.z + s2 * b.z, }; } -static inline Vec3 vec3Cross(Vec3 v1, Vec3 v2) { - return (Vec3){ +static inline Vec3d vec3Cross(Vec3d v1, Vec3d v2) { + return (Vec3d){ v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x, }; } -static inline double vec3Dot(Vec3 v1, Vec3 v2) { +static inline double vec3Dot(Vec3d v1, Vec3d v2) { return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; } -static inline double vec3NormSq(Vec3 v) { return vec3Dot(v, v); } +static inline double vec3NormSq(Vec3d v) { return vec3Dot(v, v); } -static inline double vec3Norm(Vec3 v) { return sqrt(vec3NormSq(v)); } +static inline double vec3Norm(Vec3d v) { return sqrt(vec3NormSq(v)); } -static inline void vec3Normalize(Vec3 *v) { +static inline void vec3Normalize(Vec3d *v) { double norm = vec3Norm(*v); if (norm == 0.0) return; double inv = 1.0 / norm; @@ -90,8 +90,8 @@ static inline void vec3Normalize(Vec3 *v) { v->z *= inv; } -static inline double vec3DistSq(Vec3 v1, Vec3 v2) { - Vec3 d = vec3LinComb(1.0, v1, -1.0, v2); +static inline double vec3DistSq(Vec3d v1, Vec3d v2) { + Vec3d d = vec3LinComb(1.0, v1, -1.0, v2); return vec3NormSq(d); } diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 7d4146a833..653f9f55f7 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -61,7 +61,7 @@ const LatLng faceCenterGeo[NUM_ICOSA_FACES] = { }; /** @brief icosahedron face centers in x/y/z on the unit sphere */ -static const Vec3 faceCenterPoint[NUM_ICOSA_FACES] = { +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 @@ -365,11 +365,11 @@ static const int unitScaleByCIIres[] = { * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing the squared euclidean distance to that face center. * - * @param v3 The Vec3 coordinates to encode. + * @param v3 The Vec3d coordinates to encode. * @param face Output: the icosahedral face containing the coordinates. * @param sqd Output: the squared euclidean distance to its face center. */ -static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { +static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd) { *face = 0; // The distance between two farthest points is 2.0, therefore the square of // the distance between two points should always be less or equal than 4.0 . @@ -394,8 +394,8 @@ static void _vec3ToClosestFace(const Vec3 *v3, int *face, double *sqd) { * @param north Output: local north direction on tangent plane. * @param east Output: local east direction on tangent plane. */ -static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { - Vec3 northPole = {0.0, 0.0, 1.0}; +static void _vec3TangentBasis(const Vec3d *p, Vec3d *north, Vec3d *east) { + Vec3d northPole = {0.0, 0.0, 1.0}; double NdotP = vec3Dot(northPole, *p); *north = vec3LinComb(1.0, northPole, -NdotP, *p); vec3Normalize(north); @@ -408,13 +408,13 @@ static void _vec3TangentBasis(const Vec3 *p, Vec3 *north, Vec3 *east) { * @param p2 The second vector. * @return The azimuth in radians. */ -static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { - Vec3 northDir, eastDir; +static double _vec3AzimuthRads(const Vec3d *p1, const Vec3d *p2) { + Vec3d northDir, eastDir; _vec3TangentBasis(p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 double p2dotp1 = vec3Dot(*p2, *p1); - Vec3 p2_on_tangent = vec3LinComb(1.0, *p2, -p2dotp1, *p1); + Vec3d p2_on_tangent = vec3LinComb(1.0, *p2, -p2dotp1, *p1); vec3Normalize(&p2_on_tangent); return atan2(vec3Dot(p2_on_tangent, eastDir), @@ -425,12 +425,12 @@ static double _vec3AzimuthRads(const Vec3 *p1, const Vec3 *p2) { * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing 2D hex coordinates relative to that face center. * - * @param p The Vec3 coordinates to encode. + * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. * @param face Output: the icosahedral face containing the coordinates. * @param v Output: the 2D hex coordinates of the cell containing the point. */ -static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { +static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2 *v) { // determine the icosahedron face double sqd; _vec3ToClosestFace(p, face, &sqd); @@ -467,14 +467,14 @@ static void _vec3ToVec2(const Vec3 *p, int res, int *face, Vec2 *v) { } /** - * Encodes a Vec3 coordinate to the FaceIJK address of the containing cell at + * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at * the specified resolution. * - * @param p The Vec3 coordinates to encode. + * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. * @param h Output: the FaceIJK address of the containing cell. */ -void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h) { +void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { Vec2 v; _vec3ToVec2(&p, res, &h->face, &v); _vec2ToCoordIJK(&v, &h->coord); @@ -484,7 +484,7 @@ void _vec3ToFaceIjk(Vec3 p, int res, FaceIJK *h) { * Determines the 3D coordinates of a point given by 2D hex coordinates * on a particular icosahedral face. */ -Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { +Vec3d _vec2ToVec3(Vec2 v, int face, int res, int substrate) { double r = _vec2Norm(&v); if (r < EPSILON) { @@ -516,13 +516,13 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); // now find the point at (r,theta) from the face center - Vec3 center = faceCenterPoint[face]; - Vec3 northDir, eastDir; + Vec3d center = faceCenterPoint[face]; + Vec3d northDir, eastDir; _vec3TangentBasis(¢er, &northDir, &eastDir); - Vec3 dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); + Vec3d dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); - Vec3 result = vec3LinComb(cos(r), center, sin(r), dir); + Vec3d result = vec3LinComb(cos(r), center, sin(r), dir); vec3Normalize(&result); return result; } @@ -534,7 +534,7 @@ Vec3 _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. */ -Vec3 _faceIjkToVec3(const FaceIJK *h, int res) { +Vec3d _faceIjkToVec3(const FaceIJK *h, int res) { Vec2 v; _ijkToVec2(&h->coord, &v); return _vec2ToVec3(v, h->face, res, 0); diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 33c51dd9ba..633c9106c5 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1044,7 +1044,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { return E_LATLNG_DOMAIN; } - Vec3 v = latLngToVec3(*g); + Vec3d v = latLngToVec3(*g); return vec3ToCell(&v, res, out); } @@ -1059,7 +1059,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { * @param out The encoded H3Index. * @returns E_SUCCESS on success, another value otherwise */ -H3Error vec3ToCell(const Vec3 *v, int res, H3Index *out) { +H3Error vec3ToCell(const Vec3d *v, int res, H3Index *out) { if (res < 0 || res > MAX_H3_RES) { return E_RES_DOMAIN; } @@ -1173,7 +1173,7 @@ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { * @param g The spherical coordinates of the H3 cell center. */ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { - Vec3 v; + Vec3d v; H3Error e = cellToVec3(h3, &v); if (e) { return e; @@ -1189,7 +1189,7 @@ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { * @param v The 3D cartesian coordinates of the H3 cell center. * @return E_SUCCESS on success, or another H3Error code on failure. */ -H3Error cellToVec3(H3Index h3, Vec3 *v) { +H3Error cellToVec3(H3Index h3, Vec3d *v) { FaceIJK fijk; H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { From 82eb168f1c4f5aa94d6ca6241e2cf6366b68c53d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:24:20 -0700 Subject: [PATCH 45/91] test files --- CMakeLists.txt | 4 ++-- CMakeTests.cmake | 4 ++-- src/apps/testapps/{testVec2Internal.c => testVec2dInternal.c} | 0 src/apps/testapps/{testVec3Internal.c => testVec3dInternal.c} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename src/apps/testapps/{testVec2Internal.c => testVec2dInternal.c} (100%) rename src/apps/testapps/{testVec3Internal.c => testVec3dInternal.c} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index da7f09934c..795bbdb05a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,8 +252,8 @@ set(OTHER_SOURCE_FILES src/apps/testapps/testVertexExhaustive.c src/apps/testapps/testPolygonInternal.c src/apps/testapps/testPolyfillInternal.c - src/apps/testapps/testVec2Internal.c - src/apps/testapps/testVec3Internal.c + src/apps/testapps/testVec2dInternal.c + src/apps/testapps/testVec3dInternal.c src/apps/testapps/testVec3.c src/apps/testapps/testDirectedEdge.c src/apps/testapps/testDirectedEdgeExhaustive.c diff --git a/CMakeTests.cmake b/CMakeTests.cmake index e5352c8f3e..7500b47297 100644 --- a/CMakeTests.cmake +++ b/CMakeTests.cmake @@ -240,8 +240,8 @@ add_h3_test(testVertex src/apps/testapps/testVertex.c) add_h3_test(testVertexInternal src/apps/testapps/testVertexInternal.c) add_h3_test(testPolygonInternal src/apps/testapps/testPolygonInternal.c) add_h3_test(testPolyfillInternal src/apps/testapps/testPolyfillInternal.c) -add_h3_test(testVec2Internal src/apps/testapps/testVec2Internal.c) -add_h3_test(testVec3Internal src/apps/testapps/testVec3Internal.c) +add_h3_test(testVec2dInternal src/apps/testapps/testVec2dInternal.c) +add_h3_test(testVec3dInternal src/apps/testapps/testVec3dInternal.c) add_h3_test(testVec3 src/apps/testapps/testVec3.c) add_h3_test(testCellToLocalIj src/apps/testapps/testCellToLocalIj.c) add_h3_test(testCellToLocalIjInternal diff --git a/src/apps/testapps/testVec2Internal.c b/src/apps/testapps/testVec2dInternal.c similarity index 100% rename from src/apps/testapps/testVec2Internal.c rename to src/apps/testapps/testVec2dInternal.c diff --git a/src/apps/testapps/testVec3Internal.c b/src/apps/testapps/testVec3dInternal.c similarity index 100% rename from src/apps/testapps/testVec3Internal.c rename to src/apps/testapps/testVec3dInternal.c From ddff3a20c3480bc81474f21254d1a7a8b7e7b0c2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:27:08 -0700 Subject: [PATCH 46/91] vec2d.h --- CMakeLists.txt | 4 ++-- src/apps/testapps/testVec2dInternal.c | 2 +- src/h3lib/include/coordijk.h | 2 +- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/{vec2.h => vec2d.h} | 4 ++-- src/h3lib/lib/{vec2.c => vec2d.c} | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename src/h3lib/include/{vec2.h => vec2d.h} (97%) rename src/h3lib/lib/{vec2.c => vec2d.c} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 795bbdb05a..f2d9e0e93c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -157,7 +157,7 @@ set(LIB_SOURCE_FILES src/h3lib/include/h3Index.h src/h3lib/include/directedEdge.h src/h3lib/include/latLng.h - src/h3lib/include/vec2.h + src/h3lib/include/vec2d.h src/h3lib/include/vec3d.h src/h3lib/include/linkedGeo.h src/h3lib/include/localij.h @@ -178,7 +178,7 @@ set(LIB_SOURCE_FILES src/h3lib/lib/polygon.c src/h3lib/lib/polyfill.c src/h3lib/lib/h3Index.c - src/h3lib/lib/vec2.c + src/h3lib/lib/vec2d.c src/h3lib/lib/coordijk.c src/h3lib/lib/vertex.c src/h3lib/lib/linkedGeo.c diff --git a/src/apps/testapps/testVec2dInternal.c b/src/apps/testapps/testVec2dInternal.c index 01485102e7..597c3accd7 100644 --- a/src/apps/testapps/testVec2dInternal.c +++ b/src/apps/testapps/testVec2dInternal.c @@ -19,7 +19,7 @@ #include #include "test.h" -#include "vec2.h" +#include "vec2d.h" SUITE(Vec2Internal) { TEST(_vec2Norm) { diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index a195284805..a2c3ee4f4d 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -32,7 +32,7 @@ #include "h3api.h" #include "latLng.h" -#include "vec2.h" +#include "vec2d.h" /** @struct CoordIJK * @brief IJK hexagon coordinates diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 21782f0383..df238a9bcb 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -26,7 +26,7 @@ #include "coordijk.h" #include "latLng.h" -#include "vec2.h" +#include "vec2d.h" #include "vec3d.h" /** @struct FaceIJK diff --git a/src/h3lib/include/vec2.h b/src/h3lib/include/vec2d.h similarity index 97% rename from src/h3lib/include/vec2.h rename to src/h3lib/include/vec2d.h index 5a1c1a3fec..0aac86ace0 100644 --- a/src/h3lib/include/vec2.h +++ b/src/h3lib/include/vec2d.h @@ -17,8 +17,8 @@ * @brief 2D floating point vector functions. */ -#ifndef VEC2_H -#define VEC2_H +#ifndef VEC2D_H +#define VEC2D_H #include diff --git a/src/h3lib/lib/vec2.c b/src/h3lib/lib/vec2d.c similarity index 99% rename from src/h3lib/lib/vec2.c rename to src/h3lib/lib/vec2d.c index 173c01c0ec..130f09439a 100644 --- a/src/h3lib/lib/vec2.c +++ b/src/h3lib/lib/vec2d.c @@ -17,7 +17,7 @@ * @brief 2D floating point vector functions. */ -#include "vec2.h" +#include "vec2d.h" #include #include From 8ca67fcbf4e846b61ee46d18e94d58e5f48fb34d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:29:11 -0700 Subject: [PATCH 47/91] Vec2d --- src/apps/testapps/testVec2dInternal.c | 22 ++++++------ src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 8 ++--- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/vec2d.h | 14 ++++---- src/h3lib/lib/coordijk.c | 4 +-- src/h3lib/lib/faceijk.c | 50 +++++++++++++-------------- src/h3lib/lib/vec2d.c | 10 +++--- 8 files changed, 56 insertions(+), 56 deletions(-) diff --git a/src/apps/testapps/testVec2dInternal.c b/src/apps/testapps/testVec2dInternal.c index 597c3accd7..8dbbbe5581 100644 --- a/src/apps/testapps/testVec2dInternal.c +++ b/src/apps/testapps/testVec2dInternal.c @@ -23,18 +23,18 @@ SUITE(Vec2Internal) { TEST(_vec2Norm) { - Vec2 v = {3.0, 4.0}; + Vec2d v = {3.0, 4.0}; double expected = 5.0; double mag = _vec2Norm(&v); t_assert(fabs(mag - expected) < DBL_EPSILON, "magnitude as expected"); } TEST(_vec2Intersect) { - Vec2 p0 = {2.0, 2.0}; - Vec2 p1 = {6.0, 6.0}; - Vec2 p2 = {0.0, 4.0}; - Vec2 p3 = {10.0, 4.0}; - Vec2 intersection = {0.0, 0.0}; + Vec2d p0 = {2.0, 2.0}; + Vec2d p1 = {6.0, 6.0}; + Vec2d p2 = {0.0, 4.0}; + Vec2d p3 = {10.0, 4.0}; + Vec2d intersection = {0.0, 0.0}; _vec2Intersect(&p0, &p1, &p2, &p3, &intersection); @@ -48,11 +48,11 @@ SUITE(Vec2Internal) { } TEST(_vec2AlmostEquals) { - Vec2 v1 = {3.0, 4.0}; - Vec2 v2 = {3.0, 4.0}; - Vec2 v3 = {3.5, 4.0}; - Vec2 v4 = {3.0, 4.5}; - Vec2 v5 = {3.0 + DBL_EPSILON, 4.0 - DBL_EPSILON}; + Vec2d v1 = {3.0, 4.0}; + Vec2d v2 = {3.0, 4.0}; + Vec2d v3 = {3.5, 4.0}; + Vec2d v4 = {3.0, 4.5}; + Vec2d v5 = {3.0 + DBL_EPSILON, 4.0 - DBL_EPSILON}; t_assert(_vec2AlmostEquals(&v1, &v2), "true for equal vectors"); t_assert(!_vec2AlmostEquals(&v1, &v3), "false for different x"); diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index 703888bfc8..2e65514c94 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -66,7 +66,7 @@ /** earth radius in kilometers using WGS84 authalic radius */ #define EARTH_RADIUS_KM 6371.007180918475 -/** scaling factor from Vec2 resolution 0 unit length +/** scaling factor from Vec2d resolution 0 unit length * (or distance between adjacent cell center points * on the plane) to gnomonic unit length. */ #define RES0_U_GNOMONIC 0.38196601125010500003 diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index a2c3ee4f4d..f54ba79a39 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -16,13 +16,13 @@ /** @file coordijk.h * @brief Header file for CoordIJK functions including conversion from lat/lng * - * References two Vec2 cartesian coordinate systems: + * 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. Vec2: local face-centered coordinate system scaled a specific H3 grid + * 2. Vec2d: local face-centered coordinate system scaled a specific H3 grid * resolution unit length and with x-axes aligned with the local * i-axes */ @@ -87,8 +87,8 @@ typedef enum { // Internal functions void _setIJK(CoordIJK *ijk, int i, int j, int k); -void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h); -void _ijkToVec2(const CoordIJK *h, Vec2 *v); +void _vec2ToCoordIJK(const Vec2d *v, CoordIJK *h); +void _ijkToVec2(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); diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index df238a9bcb..1c25064772 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -16,7 +16,7 @@ /** @file faceijk.h * @brief FaceIJK functions including conversion to/from lat/lng. * - * References the Vec2 cartesian coordinate system: local face-centered + * References the Vec2d cartesian coordinate system: local face-centered * coordinate system scaled a specific H3 grid resolution unit length and * with x-axes aligned with the local i-axes */ diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2d.h index 0aac86ace0..e54170fa62 100644 --- a/src/h3lib/include/vec2d.h +++ b/src/h3lib/include/vec2d.h @@ -22,23 +22,23 @@ #include -/** @struct Vec2 +/** @struct Vec2d * @brief 2D floating-point vector * - * Represents a point in the face-local Vec2 coordinate system: + * Represents a point in the face-local Vec2d coordinate system: * an orthogonal 2D plane centered on an icosahedron face, with the * x-axis aligned to the face's i-axis and y perpendicular to it. */ typedef struct { double x; /// aligned with face i-axis double y; /// perpendicular to face i-axis -} Vec2; +} Vec2d; // Internal functions -double _vec2Norm(const Vec2 *v); -void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, - const Vec2 *p3, Vec2 *inter); -bool _vec2AlmostEquals(const Vec2 *p0, const Vec2 *p1); +double _vec2Norm(const Vec2d *v); +void _vec2Intersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter); +bool _vec2AlmostEquals(const Vec2d *p0, const Vec2d *p1); #endif diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index 1c6c52e375..cec1d79e71 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -53,7 +53,7 @@ void _setIJK(CoordIJK *ijk, int i, int j, int k) { * @param v The 2D cartesian coordinate vector. * @param h The ijk+ coordinates of the containing hex. */ -void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { +void _vec2ToCoordIJK(const Vec2d *v, CoordIJK *h) { double a1, a2; double x1, x2; int m1, m2; @@ -152,7 +152,7 @@ void _vec2ToCoordIJK(const Vec2 *v, CoordIJK *h) { * @param h The ijk coordinates of the hex. * @param v The 2D cartesian coordinates of the hex center point. */ -void _ijkToVec2(const CoordIJK *h, Vec2 *v) { +void _ijkToVec2(const CoordIJK *h, Vec2d *v) { int i = h->i - h->k; int j = h->j - h->k; diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 653f9f55f7..b8545c58f7 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -430,7 +430,7 @@ static double _vec3AzimuthRads(const Vec3d *p1, const Vec3d *p2) { * @param face Output: the icosahedral face containing the coordinates. * @param v Output: the 2D hex coordinates of the cell containing the point. */ -static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2 *v) { +static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2d *v) { // determine the icosahedron face double sqd; _vec3ToClosestFace(p, face, &sqd); @@ -459,7 +459,7 @@ static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2 *v) { r *= INV_RES0_U_GNOMONIC; for (int i = 0; i < res; i++) r *= M_SQRT7; - // we now have (r, theta) in Vec2 with theta ccw from x-axes + // we now have (r, theta) in Vec2d with theta ccw from x-axes // convert to local x,y v->x = r * cos(theta); @@ -475,7 +475,7 @@ static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2 *v) { * @param h Output: the FaceIJK address of the containing cell. */ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { - Vec2 v; + Vec2d v; _vec3ToVec2(&p, res, &h->face, &v); _vec2ToCoordIJK(&v, &h->coord); } @@ -484,7 +484,7 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { * Determines the 3D coordinates of a point given by 2D hex coordinates * on a particular icosahedral face. */ -Vec3d _vec2ToVec3(Vec2 v, int face, int res, int substrate) { +Vec3d _vec2ToVec3(Vec2d v, int face, int res, int substrate) { double r = _vec2Norm(&v); if (r < EPSILON) { @@ -535,7 +535,7 @@ Vec3d _vec2ToVec3(Vec2 v, int face, int res, int substrate) { * @param res The H3 resolution of the cell. */ Vec3d _faceIjkToVec3(const FaceIJK *h, int res) { - Vec2 v; + Vec2d v; _ijkToVec2(&h->coord, &v); return _vec2ToVec3(v, h->face, res, 0); } @@ -578,11 +578,11 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // note that Class II pentagons have vertices on the edge, // not edge intersections if (isResolutionClassIII(res) && vert > start) { - // find Vec2 of the two vertexes on the last face + // find Vec2d of the two vertexes on the last face FaceIJK tmpFijk = fijk; - Vec2 orig2d0; + Vec2d orig2d0; _ijkToVec2(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -601,17 +601,17 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkAdd(ijk, &transVec, ijk); _ijkNormalize(ijk); - Vec2 orig2d1; + Vec2d orig2d1; _ijkToVec2(ijk, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; - Vec2 v0 = {3.0 * maxDim, 0.0}; - Vec2 v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; - Vec2 v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; + 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}; - Vec2 *edge0; - Vec2 *edge1; + Vec2d *edge0; + Vec2d *edge1; switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { case IJ: edge0 = &v0; @@ -630,7 +630,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, } // find the intersection and add the lat/lng point to the result - Vec2 inter; + Vec2d inter; _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(inter, tmpFijk.face, adjRes, 1)); @@ -641,7 +641,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_PENT_VERTS) { - Vec2 vec; + Vec2d vec; _ijkToVec2(&fijk.coord, &vec); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); @@ -760,23 +760,23 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, */ if (isResolutionClassIII(res) && vert > start && fijk.face != lastFace && lastOverage != FACE_EDGE) { - // find Vec2 of the two vertexes on original face + // find Vec2d of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; - Vec2 orig2d0; + Vec2d orig2d0; _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); - Vec2 orig2d1; + Vec2d orig2d1; _ijkToVec2(&fijkVerts[v].coord, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; - Vec2 v0 = {3.0 * maxDim, 0.0}; - Vec2 v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; - Vec2 v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; + 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); - Vec2 *edge0; - Vec2 *edge1; + Vec2d *edge0; + Vec2d *edge1; switch (adjacentFaceDir[centerIJK.face][face2]) { case IJ: edge0 = &v0; @@ -795,7 +795,7 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, } // find the intersection and add the lat/lng point to the result - Vec2 inter; + Vec2d inter; _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); /* If a point of intersection occurs at a hexagon vertex, then each @@ -815,7 +815,7 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + NUM_HEX_VERTS) { - Vec2 vec; + Vec2d vec; _ijkToVec2(&fijk.coord, &vec); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); diff --git a/src/h3lib/lib/vec2d.c b/src/h3lib/lib/vec2d.c index 130f09439a..bcb74e2f22 100644 --- a/src/h3lib/lib/vec2d.c +++ b/src/h3lib/lib/vec2d.c @@ -28,7 +28,7 @@ * @param v The 2D cartesian vector. * @return The magnitude of the vector. */ -double _vec2Norm(const Vec2 *v) { return sqrt(v->x * v->x + v->y * v->y); } +double _vec2Norm(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } /** * Finds the intersection between two lines. Assumes that the lines intersect @@ -39,9 +39,9 @@ double _vec2Norm(const Vec2 *v) { return sqrt(v->x * v->x + v->y * v->y); } * @param p3 The second endpoint of the second line. * @param inter The intersection point. */ -void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, - const Vec2 *p3, Vec2 *inter) { - Vec2 s1, s2; +void _vec2Intersect(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; @@ -61,7 +61,7 @@ void _vec2Intersect(const Vec2 *p0, const Vec2 *p1, const Vec2 *p2, * @param v2 Second vector to compare * @return Whether the vectors are almost equal */ -bool _vec2AlmostEquals(const Vec2 *v1, const Vec2 *v2) { +bool _vec2AlmostEquals(const Vec2d *v1, const Vec2d *v2) { return fabs(v1->x - v2->x) < FLT_EPSILON && fabs(v1->y - v2->y) < FLT_EPSILON; } From d64eb6a5c937ccd8ebd3503ddd376f7429d4dcfb Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:33:52 -0700 Subject: [PATCH 48/91] vec2 names --- .../testapps/testCellToBoundaryEdgeCases.c | 2 +- src/apps/testapps/testVec2dInternal.c | 18 +++++++++--------- src/h3lib/include/vec2d.h | 8 ++++---- src/h3lib/lib/faceijk.c | 10 +++++----- src/h3lib/lib/vec2d.c | 8 ++++---- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/apps/testapps/testCellToBoundaryEdgeCases.c b/src/apps/testapps/testCellToBoundaryEdgeCases.c index 2cf62f4cbb..55a3bec3ad 100644 --- a/src/apps/testapps/testCellToBoundaryEdgeCases.c +++ b/src/apps/testapps/testCellToBoundaryEdgeCases.c @@ -28,7 +28,7 @@ SUITE(cellToBoundaryEdgeCases) { TEST(doublePrecisionVertex) { // The carefully constructed case here: // - A res 1 pentagon cell with distortion vertexes that change - // when we use a double instead of a float in _vec2Intersect + // when we use a double instead of a float in _v2dIntersect // - One of the previous (float-based) distortion vertexes // This is the only case yet found where a point indexed to the // cell is shown to be incorrectly outside of the geo boundary diff --git a/src/apps/testapps/testVec2dInternal.c b/src/apps/testapps/testVec2dInternal.c index 8dbbbe5581..a6a1874168 100644 --- a/src/apps/testapps/testVec2dInternal.c +++ b/src/apps/testapps/testVec2dInternal.c @@ -22,21 +22,21 @@ #include "vec2d.h" SUITE(Vec2Internal) { - TEST(_vec2Norm) { + TEST(_v2dMag) { Vec2d v = {3.0, 4.0}; double expected = 5.0; - double mag = _vec2Norm(&v); + double mag = _v2dMag(&v); t_assert(fabs(mag - expected) < DBL_EPSILON, "magnitude as expected"); } - TEST(_vec2Intersect) { + TEST(_v2dIntersect) { Vec2d p0 = {2.0, 2.0}; Vec2d p1 = {6.0, 6.0}; Vec2d p2 = {0.0, 4.0}; Vec2d p3 = {10.0, 4.0}; Vec2d intersection = {0.0, 0.0}; - _vec2Intersect(&p0, &p1, &p2, &p3, &intersection); + _v2dIntersect(&p0, &p1, &p2, &p3, &intersection); double expectedX = 4.0; double expectedY = 4.0; @@ -47,16 +47,16 @@ SUITE(Vec2Internal) { "Y coord as expected"); } - TEST(_vec2AlmostEquals) { + TEST(_v2dAlmostEquals) { Vec2d v1 = {3.0, 4.0}; Vec2d v2 = {3.0, 4.0}; Vec2d v3 = {3.5, 4.0}; Vec2d v4 = {3.0, 4.5}; Vec2d v5 = {3.0 + DBL_EPSILON, 4.0 - DBL_EPSILON}; - t_assert(_vec2AlmostEquals(&v1, &v2), "true for equal vectors"); - t_assert(!_vec2AlmostEquals(&v1, &v3), "false for different x"); - t_assert(!_vec2AlmostEquals(&v1, &v4), "false for different y"); - t_assert(_vec2AlmostEquals(&v1, &v5), "true for almost equal"); + t_assert(_v2dAlmostEquals(&v1, &v2), "true for equal vectors"); + t_assert(!_v2dAlmostEquals(&v1, &v3), "false for different x"); + t_assert(!_v2dAlmostEquals(&v1, &v4), "false for different y"); + t_assert(_v2dAlmostEquals(&v1, &v5), "true for almost equal"); } } diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2d.h index e54170fa62..1aec6e1bbb 100644 --- a/src/h3lib/include/vec2d.h +++ b/src/h3lib/include/vec2d.h @@ -36,9 +36,9 @@ typedef struct { // Internal functions -double _vec2Norm(const Vec2d *v); -void _vec2Intersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, - const Vec2d *p3, Vec2d *inter); -bool _vec2AlmostEquals(const Vec2d *p0, const Vec2d *p1); +double _v2dMag(const Vec2d *v); +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter); +bool _v2dAlmostEquals(const Vec2d *p0, const Vec2d *p1); #endif diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index b8545c58f7..812a63bf2b 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -485,7 +485,7 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { * on a particular icosahedral face. */ Vec3d _vec2ToVec3(Vec2d v, int face, int res, int substrate) { - double r = _vec2Norm(&v); + double r = _v2dMag(&v); if (r < EPSILON) { return faceCenterPoint[face]; @@ -631,7 +631,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // find the intersection and add the lat/lng point to the result Vec2d inter; - _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &inter); + _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(inter, tmpFijk.face, adjRes, 1)); g->numVerts++; @@ -796,14 +796,14 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, // find the intersection and add the lat/lng point to the result Vec2d inter; - _vec2Intersect(&orig2d0, &orig2d1, edge0, edge1, &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 = _vec2AlmostEquals(&orig2d0, &inter) || - _vec2AlmostEquals(&orig2d1, &inter); + bool isIntersectionAtVertex = _v2dAlmostEquals(&orig2d0, &inter) || + _v2dAlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(inter, centerIJK.face, adjRes, 1)); diff --git a/src/h3lib/lib/vec2d.c b/src/h3lib/lib/vec2d.c index bcb74e2f22..a7ca258ee5 100644 --- a/src/h3lib/lib/vec2d.c +++ b/src/h3lib/lib/vec2d.c @@ -28,7 +28,7 @@ * @param v The 2D cartesian vector. * @return The magnitude of the vector. */ -double _vec2Norm(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } +double _v2dMag(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } /** * Finds the intersection between two lines. Assumes that the lines intersect @@ -39,8 +39,8 @@ double _vec2Norm(const Vec2d *v) { return sqrt(v->x * v->x + v->y * v->y); } * @param p3 The second endpoint of the second line. * @param inter The intersection point. */ -void _vec2Intersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, - const Vec2d *p3, Vec2d *inter) { +void _v2dIntersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, + const Vec2d *p3, Vec2d *inter) { Vec2d s1, s2; s1.x = p1->x - p0->x; s1.y = p1->y - p0->y; @@ -61,7 +61,7 @@ void _vec2Intersect(const Vec2d *p0, const Vec2d *p1, const Vec2d *p2, * @param v2 Second vector to compare * @return Whether the vectors are almost equal */ -bool _vec2AlmostEquals(const Vec2d *v1, const Vec2d *v2) { +bool _v2dAlmostEquals(const Vec2d *v1, const Vec2d *v2) { return fabs(v1->x - v2->x) < FLT_EPSILON && fabs(v1->y - v2->y) < FLT_EPSILON; } From 35a3332ffd895af4d609de8c9adc97e3e569440e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:35:47 -0700 Subject: [PATCH 49/91] more vec2 names --- src/h3lib/include/coordijk.h | 4 ++-- src/h3lib/lib/coordijk.c | 4 ++-- src/h3lib/lib/faceijk.c | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index f54ba79a39..4784335295 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -87,8 +87,8 @@ typedef enum { // Internal functions void _setIJK(CoordIJK *ijk, int i, int j, int k); -void _vec2ToCoordIJK(const Vec2d *v, CoordIJK *h); -void _ijkToVec2(const CoordIJK *h, Vec2d *v); +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); diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index cec1d79e71..f6a345d7c1 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -53,7 +53,7 @@ void _setIJK(CoordIJK *ijk, int i, int j, int k) { * @param v The 2D cartesian coordinate vector. * @param h The ijk+ coordinates of the containing hex. */ -void _vec2ToCoordIJK(const Vec2d *v, CoordIJK *h) { +void _hex2dToCoordIJK(const Vec2d *v, CoordIJK *h) { double a1, a2; double x1, x2; int m1, m2; @@ -152,7 +152,7 @@ void _vec2ToCoordIJK(const Vec2d *v, CoordIJK *h) { * @param h The ijk coordinates of the hex. * @param v The 2D cartesian coordinates of the hex center point. */ -void _ijkToVec2(const CoordIJK *h, Vec2d *v) { +void _ijkToHex2d(const CoordIJK *h, Vec2d *v) { int i = h->i - h->k; int j = h->j - h->k; diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 812a63bf2b..42df4a756a 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -477,7 +477,7 @@ static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2d *v) { void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { Vec2d v; _vec3ToVec2(&p, res, &h->face, &v); - _vec2ToCoordIJK(&v, &h->coord); + _hex2dToCoordIJK(&v, &h->coord); } /** @@ -536,7 +536,7 @@ Vec3d _vec2ToVec3(Vec2d v, int face, int res, int substrate) { */ Vec3d _faceIjkToVec3(const FaceIJK *h, int res) { Vec2d v; - _ijkToVec2(&h->coord, &v); + _ijkToHex2d(&h->coord, &v); return _vec2ToVec3(v, h->face, res, 0); } @@ -583,7 +583,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, FaceIJK tmpFijk = fijk; Vec2d orig2d0; - _ijkToVec2(&lastFijk.coord, &orig2d0); + _ijkToHex2d(&lastFijk.coord, &orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; @@ -602,7 +602,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, _ijkNormalize(ijk); Vec2d orig2d1; - _ijkToVec2(ijk, &orig2d1); + _ijkToHex2d(ijk, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -642,7 +642,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // intersection on last edge if (vert < start + NUM_PENT_VERTS) { Vec2d vec; - _ijkToVec2(&fijk.coord, &vec); + _ijkToHex2d(&fijk.coord, &vec); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; @@ -763,10 +763,10 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, // find Vec2d of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; Vec2d orig2d0; - _ijkToVec2(&fijkVerts[lastV].coord, &orig2d0); + _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); Vec2d orig2d1; - _ijkToVec2(&fijkVerts[v].coord, &orig2d1); + _ijkToHex2d(&fijkVerts[v].coord, &orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; @@ -816,7 +816,7 @@ void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, // intersection on last edge if (vert < start + NUM_HEX_VERTS) { Vec2d vec; - _ijkToVec2(&fijk.coord, &vec); + _ijkToHex2d(&fijk.coord, &vec); g->verts[g->numVerts] = vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; From ae94a89d0202cfefb7d08f007522d68aba6a0eb1 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:42:23 -0700 Subject: [PATCH 50/91] names --- src/h3lib/include/faceijk.h | 4 ++-- src/h3lib/include/vec2d.h | 12 ++++-------- src/h3lib/lib/directedEdge.c | 2 +- src/h3lib/lib/faceijk.c | 4 ++-- src/h3lib/lib/h3Index.c | 4 ++-- src/h3lib/lib/vec2d.c | 4 ++-- src/h3lib/lib/vertex.c | 2 +- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 1c25064772..145c5b66ee 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -75,8 +75,8 @@ typedef enum { void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h); Vec3d _faceIjkToVec3(const FaceIJK *h, int res); -void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, - CellBoundary *g); +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts); diff --git a/src/h3lib/include/vec2d.h b/src/h3lib/include/vec2d.h index 1aec6e1bbb..8ba0e9bd4e 100644 --- a/src/h3lib/include/vec2d.h +++ b/src/h3lib/include/vec2d.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017, 2026 Uber Technologies, Inc. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec2.h +/** @file vec2d.h * @brief 2D floating point vector functions. */ @@ -24,14 +24,10 @@ /** @struct Vec2d * @brief 2D floating-point vector - * - * Represents a point in the face-local Vec2d coordinate system: - * an orthogonal 2D plane centered on an icosahedron face, with the - * x-axis aligned to the face's i-axis and y perpendicular to it. */ typedef struct { - double x; /// aligned with face i-axis - double y; /// perpendicular to face i-axis + double x; ///< x component + double y; ///< y component } Vec2d; // Internal functions diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c index 202b437e60..1956caedae 100644 --- a/src/h3lib/lib/directedEdge.c +++ b/src/h3lib/lib/directedEdge.c @@ -288,7 +288,7 @@ H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge, CellBoundary *cb) { if (isPent) { _faceIjkPentToCellBoundary(&fijk, res, startVertex, 2, cb); } else { - _faceIjkToH3Boundary(&fijk, res, startVertex, 2, cb); + _faceIjkToCellBoundary(&fijk, res, startVertex, 2, cb); } return E_SUCCESS; } diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 42df4a756a..446959ed62 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -723,8 +723,8 @@ void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { * @param length The number of topological vertexes to return. * @param g Output: the spherical coordinates of the cell boundary. */ -void _faceIjkToH3Boundary(const FaceIJK *h, int res, int start, int length, - CellBoundary *g) { +void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, + CellBoundary *g) { int adjRes = res; FaceIJK centerIJK = *h; FaceIJK fijkVerts[NUM_HEX_VERTS]; diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 633c9106c5..d59645fa01 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1215,8 +1215,8 @@ H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { _faceIjkPentToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_PENT_VERTS, cb); } else { - _faceIjkToH3Boundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, - cb); + _faceIjkToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, + cb); } return E_SUCCESS; } diff --git a/src/h3lib/lib/vec2d.c b/src/h3lib/lib/vec2d.c index a7ca258ee5..2b4a121bf2 100644 --- a/src/h3lib/lib/vec2d.c +++ b/src/h3lib/lib/vec2d.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017, 2026 Uber Technologies, Inc. + * 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec2.c +/** @file vec2d.c * @brief 2D floating point vector functions. */ diff --git a/src/h3lib/lib/vertex.c b/src/h3lib/lib/vertex.c index 9f90a83245..a864b64242 100644 --- a/src/h3lib/lib/vertex.c +++ b/src/h3lib/lib/vertex.c @@ -338,7 +338,7 @@ H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *coord) { if (H3_EXPORT(isPentagon)(owner)) { _faceIjkPentToCellBoundary(&fijk, res, vertexNum, 1, &gb); } else { - _faceIjkToH3Boundary(&fijk, res, vertexNum, 1, &gb); + _faceIjkToCellBoundary(&fijk, res, vertexNum, 1, &gb); } // Copy from boundary to output coord From b883add350fb366d4c933fd0f2cdf6e3e98ad3e5 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:46:37 -0700 Subject: [PATCH 51/91] comments --- src/apps/testapps/testVec2dInternal.c | 4 ++-- src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 2 +- src/h3lib/include/faceijk.h | 2 +- src/h3lib/include/vec3d.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apps/testapps/testVec2dInternal.c b/src/apps/testapps/testVec2dInternal.c index a6a1874168..e1e284bd23 100644 --- a/src/apps/testapps/testVec2dInternal.c +++ b/src/apps/testapps/testVec2dInternal.c @@ -1,5 +1,5 @@ /* - * Copyright 2018, 2026 Uber Technologies, Inc. + * 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. @@ -21,7 +21,7 @@ #include "test.h" #include "vec2d.h" -SUITE(Vec2Internal) { +SUITE(Vec2dInternal) { TEST(_v2dMag) { Vec2d v = {3.0, 4.0}; double expected = 5.0; diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index 2e65514c94..5e1b994f2d 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -66,7 +66,7 @@ /** earth radius in kilometers using WGS84 authalic radius */ #define EARTH_RADIUS_KM 6371.007180918475 -/** scaling factor from Vec2d resolution 0 unit length +/** 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.38196601125010500003 diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 4784335295..5ab03efe62 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -22,7 +22,7 @@ * traditional scaling and x-axes aligned with the face Class II * i-axes. * - * 2. Vec2d: local face-centered coordinate system scaled a specific H3 grid + * 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 */ diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 145c5b66ee..25e4704bda 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -16,7 +16,7 @@ /** @file faceijk.h * @brief FaceIJK functions including conversion to/from lat/lng. * - * References the Vec2d cartesian coordinate system: local face-centered + * 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 */ diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index c4673cd0e0..7304227a2d 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** @file vec3.h +/** @file vec3d.h * @brief 3D floating point vector functions. * * Header-only (static inline) so callers in other translation units From 4ec1613dbd28f33aadaa1bcdbd8718fbc3fe73ee Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:48:07 -0700 Subject: [PATCH 52/91] years --- src/h3lib/lib/localij.c | 2 +- src/h3lib/lib/vertex.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h3lib/lib/localij.c b/src/h3lib/lib/localij.c index 7d71a7df0d..489023f034 100644 --- a/src/h3lib/lib/localij.c +++ b/src/h3lib/lib/localij.c @@ -1,5 +1,5 @@ /* - * Copyright 2018-2020, 2026 Uber Technologies, Inc. + * Copyright 2018-2020 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/h3lib/lib/vertex.c b/src/h3lib/lib/vertex.c index a864b64242..5f3755a9c9 100644 --- a/src/h3lib/lib/vertex.c +++ b/src/h3lib/lib/vertex.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021, 2026 Uber Technologies, Inc. + * Copyright 2020-2021 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From cd3b96a993e0116f6aedd24e6b35ec2f8960657f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:49:02 -0700 Subject: [PATCH 53/91] years --- src/apps/miscapps/generatePentagonDirectionFaces.c | 2 +- src/apps/testapps/testCellToBoundaryEdgeCases.c | 2 +- src/apps/testapps/testH3IndexInternal.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/apps/miscapps/generatePentagonDirectionFaces.c b/src/apps/miscapps/generatePentagonDirectionFaces.c index 789a6c5052..468d238bb5 100644 --- a/src/apps/miscapps/generatePentagonDirectionFaces.c +++ b/src/apps/miscapps/generatePentagonDirectionFaces.c @@ -1,5 +1,5 @@ /* - * Copyright 2020, 2026 Uber Technologies, Inc. + * Copyright 2020 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/apps/testapps/testCellToBoundaryEdgeCases.c b/src/apps/testapps/testCellToBoundaryEdgeCases.c index 55a3bec3ad..f7eca1742f 100644 --- a/src/apps/testapps/testCellToBoundaryEdgeCases.c +++ b/src/apps/testapps/testCellToBoundaryEdgeCases.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2021, 2026 Uber Technologies, Inc. + * Copyright 2017-2021 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/apps/testapps/testH3IndexInternal.c b/src/apps/testapps/testH3IndexInternal.c index e88bfa1975..c7ed6c5cbe 100644 --- a/src/apps/testapps/testH3IndexInternal.c +++ b/src/apps/testapps/testH3IndexInternal.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, 2020-2021, 2026 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From dd94efb2910f4156a30d2c95f9a3d94289d0b18b Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:52:03 -0700 Subject: [PATCH 54/91] boop --- CMakeLists.txt | 2 +- src/h3lib/lib/directedEdge.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f2d9e0e93c..257544b737 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,6 +169,7 @@ set(LIB_SOURCE_FILES src/h3lib/include/constants.h src/h3lib/include/coordijk.h src/h3lib/include/algos.h + src/h3lib/lib/coordijk.c src/h3lib/include/adder.h src/h3lib/include/area.h src/h3lib/include/cellsToMultiPoly.h @@ -179,7 +180,6 @@ set(LIB_SOURCE_FILES src/h3lib/lib/polyfill.c src/h3lib/lib/h3Index.c src/h3lib/lib/vec2d.c - src/h3lib/lib/coordijk.c src/h3lib/lib/vertex.c src/h3lib/lib/linkedGeo.c src/h3lib/lib/localij.c diff --git a/src/h3lib/lib/directedEdge.c b/src/h3lib/lib/directedEdge.c index 1956caedae..131a86160c 100644 --- a/src/h3lib/lib/directedEdge.c +++ b/src/h3lib/lib/directedEdge.c @@ -1,5 +1,5 @@ /* - * Copyright 2017-2018, 2020-2021, 2026 Uber Technologies, Inc. + * Copyright 2017-2018, 2020-2021 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From a27bf9e7047c42bfe9b7fc55d17ad107b1456084 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 17:53:49 -0700 Subject: [PATCH 55/91] years --- src/h3lib/include/constants.h | 2 +- src/h3lib/include/coordijk.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h3lib/include/constants.h b/src/h3lib/include/constants.h index 5e1b994f2d..90b7036aa0 100644 --- a/src/h3lib/include/constants.h +++ b/src/h3lib/include/constants.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017, 2020, 2026 Uber Technologies, Inc. + * Copyright 2016-2017, 2020 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/h3lib/include/coordijk.h b/src/h3lib/include/coordijk.h index 5ab03efe62..422fdb1c10 100644 --- a/src/h3lib/include/coordijk.h +++ b/src/h3lib/include/coordijk.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2022, 2026 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2022 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. From d3862ad9f58556a0a9f2ea217b1895d7c0434a0f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:18:41 -0700 Subject: [PATCH 56/91] old test --- src/apps/testapps/testVec3dInternal.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/apps/testapps/testVec3dInternal.c b/src/apps/testapps/testVec3dInternal.c index 5470aa85b5..58e0080da0 100644 --- a/src/apps/testapps/testVec3dInternal.c +++ b/src/apps/testapps/testVec3dInternal.c @@ -14,12 +14,32 @@ * limitations under the License. */ +#include #include #include "test.h" #include "vec3d.h" SUITE(Vec3dInternal) { + TEST(vec3DistSq) { + Vec3d v1 = {0, 0, 0}; + Vec3d v2 = {1, 0, 0}; + Vec3d v3 = {0, 1, 1}; + Vec3d v4 = {1, 1, 1}; + Vec3d v5 = {1, 1, 2}; + + t_assert(fabs(vec3DistSq(v1, v1)) < DBL_EPSILON, + "distance to self is 0"); + t_assert(fabs(vec3DistSq(v1, v2) - 1) < DBL_EPSILON, + "distance to <1,0,0> is 1"); + t_assert(fabs(vec3DistSq(v1, v3) - 2) < DBL_EPSILON, + "distance to <0,1,1> is 2"); + t_assert(fabs(vec3DistSq(v1, v4) - 3) < DBL_EPSILON, + "distance to <1,1,1> is 3"); + t_assert(fabs(vec3DistSq(v1, v5) - 6) < DBL_EPSILON, + "distance to <1,1,2> is 6"); + } + TEST(latLngToVec3) { Vec3d origin = {0}; From 6d2a8bfa9b05720e6d86de1fbb374f6f173e3038 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:20:41 -0700 Subject: [PATCH 57/91] haven't i done this one already? --- src/h3lib/lib/coordijk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/lib/coordijk.c b/src/h3lib/lib/coordijk.c index f6a345d7c1..0bed0250b5 100644 --- a/src/h3lib/lib/coordijk.c +++ b/src/h3lib/lib/coordijk.c @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018, 2020-2023, 2026 Uber Technologies, Inc. + * Copyright 2016-2018, 2020-2023 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. From d3ac59723a8f16639b2d2d85705db087716e7bce Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:33:12 -0700 Subject: [PATCH 58/91] _hex2dToVec3 --- src/h3lib/lib/faceijk.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 446959ed62..7b89f9efd8 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -484,7 +484,7 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { * Determines the 3D coordinates of a point given by 2D hex coordinates * on a particular icosahedral face. */ -Vec3d _vec2ToVec3(Vec2d v, int face, int res, int substrate) { +Vec3d _hex2dToVec3(Vec2d v, int face, int res, int substrate) { double r = _v2dMag(&v); if (r < EPSILON) { @@ -537,7 +537,7 @@ Vec3d _vec2ToVec3(Vec2d v, int face, int res, int substrate) { Vec3d _faceIjkToVec3(const FaceIJK *h, int res) { Vec2d v; _ijkToHex2d(&h->coord, &v); - return _vec2ToVec3(v, h->face, res, 0); + return _hex2dToVec3(v, h->face, res, 0); } /** @@ -578,7 +578,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // note that Class II pentagons have vertices on the edge, // not edge intersections if (isResolutionClassIII(res) && vert > start) { - // find Vec2d of the two vertexes on the last face + // find hex2d of the two vertexes on the last face FaceIJK tmpFijk = fijk; @@ -633,7 +633,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(inter, tmpFijk.face, adjRes, 1)); + vec3ToLatLng(_hex2dToVec3(inter, tmpFijk.face, adjRes, 1)); g->numVerts++; } @@ -644,7 +644,7 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + vec3ToLatLng(_hex2dToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } @@ -805,8 +805,8 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, bool isIntersectionAtVertex = _v2dAlmostEquals(&orig2d0, &inter) || _v2dAlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { - g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(inter, centerIJK.face, adjRes, 1)); + g->verts[g->numVerts] = vec3ToLatLng( + _hex2dToVec3(inter, centerIJK.face, adjRes, 1)); g->numVerts++; } } @@ -818,7 +818,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); g->verts[g->numVerts] = - vec3ToLatLng(_vec2ToVec3(vec, fijk.face, adjRes, 1)); + vec3ToLatLng(_hex2dToVec3(vec, fijk.face, adjRes, 1)); g->numVerts++; } From 93e67a30de0ed53ce1235ab079d45073a817e059 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:37:58 -0700 Subject: [PATCH 59/91] pass output by reference --- src/h3lib/include/faceijk.h | 2 +- src/h3lib/lib/faceijk.c | 49 +++++++++++++++++++++++-------------- src/h3lib/lib/h3Index.c | 2 +- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 25e4704bda..450d81dc3e 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -74,7 +74,7 @@ typedef enum { // Internal functions void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h); -Vec3d _faceIjkToVec3(const FaceIJK *h, int res); +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 7b89f9efd8..cdf5d91a0e 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -483,15 +483,22 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { /** * Determines the 3D coordinates of a point given by 2D hex coordinates * on a particular icosahedral face. + * + * @param v The 2D hex coordinates of the point. + * @param face The icosahedral face. + * @param res The H3 resolution of the cell. + * @param substrate Whether this is a substrate grid. + * @param v3 Output: the 3D coordinates of the point. */ -Vec3d _hex2dToVec3(Vec2d v, int face, int res, int substrate) { - double r = _v2dMag(&v); +void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { + double r = _v2dMag(v); if (r < EPSILON) { - return faceCenterPoint[face]; + *v3 = faceCenterPoint[face]; + return; } - double theta = atan2(v.y, v.x); + double theta = atan2(v->y, v->x); // scale for current resolution length u for (int i = 0; i < res; i++) r *= M_RSQRT7; @@ -516,15 +523,14 @@ Vec3d _hex2dToVec3(Vec2d v, int face, int res, int substrate) { theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); // now find the point at (r,theta) from the face center - Vec3d center = faceCenterPoint[face]; + const Vec3d *center = &faceCenterPoint[face]; Vec3d northDir, eastDir; - _vec3TangentBasis(¢er, &northDir, &eastDir); + _vec3TangentBasis(center, &northDir, &eastDir); Vec3d dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); - Vec3d result = vec3LinComb(cos(r), center, sin(r), dir); - vec3Normalize(&result); - return result; + *v3 = vec3LinComb(cos(r), *center, sin(r), dir); + vec3Normalize(v3); } /** @@ -533,11 +539,12 @@ Vec3d _hex2dToVec3(Vec2d v, int face, int res, int substrate) { * * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. + * @param v3 Output: the 3D coordinates of the cell center point. */ -Vec3d _faceIjkToVec3(const FaceIJK *h, int res) { +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3) { Vec2d v; _ijkToHex2d(&h->coord, &v); - return _hex2dToVec3(v, h->face, res, 0); + _hex2dToVec3(&v, h->face, res, 0, v3); } /** @@ -632,8 +639,9 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, // find the intersection and add the lat/lng point to the result Vec2d inter; _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); - g->verts[g->numVerts] = - vec3ToLatLng(_hex2dToVec3(inter, tmpFijk.face, adjRes, 1)); + Vec3d v3; + _hex2dToVec3(&inter, tmpFijk.face, adjRes, 1, &v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } @@ -643,8 +651,9 @@ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, if (vert < start + NUM_PENT_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_hex2dToVec3(vec, fijk.face, adjRes, 1)); + Vec3d v3; + _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } @@ -805,8 +814,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, bool isIntersectionAtVertex = _v2dAlmostEquals(&orig2d0, &inter) || _v2dAlmostEquals(&orig2d1, &inter); if (!isIntersectionAtVertex) { - g->verts[g->numVerts] = vec3ToLatLng( - _hex2dToVec3(inter, centerIJK.face, adjRes, 1)); + Vec3d v3; + _hex2dToVec3(&inter, centerIJK.face, adjRes, 1, &v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } } @@ -817,8 +827,9 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, if (vert < start + NUM_HEX_VERTS) { Vec2d vec; _ijkToHex2d(&fijk.coord, &vec); - g->verts[g->numVerts] = - vec3ToLatLng(_hex2dToVec3(vec, fijk.face, adjRes, 1)); + Vec3d v3; + _hex2dToVec3(&vec, fijk.face, adjRes, 1, &v3); + g->verts[g->numVerts] = vec3ToLatLng(v3); g->numVerts++; } diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index d59645fa01..f8bee7f738 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1195,7 +1195,7 @@ H3Error cellToVec3(H3Index h3, Vec3d *v) { if (e) { return e; } - *v = _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3)); + _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3), v); return E_SUCCESS; } From 72d41f43697b65c0dc22208664783f86963526ef Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:41:06 -0700 Subject: [PATCH 60/91] _vec3ToHex2d --- src/h3lib/lib/faceijk.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index cdf5d91a0e..f1bb2b3e28 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -430,7 +430,7 @@ static double _vec3AzimuthRads(const Vec3d *p1, const Vec3d *p2) { * @param face Output: the icosahedral face containing the coordinates. * @param v Output: the 2D hex coordinates of the cell containing the point. */ -static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2d *v) { +static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { // determine the icosahedron face double sqd; _vec3ToClosestFace(p, face, &sqd); @@ -476,7 +476,7 @@ static void _vec3ToVec2(const Vec3d *p, int res, int *face, Vec2d *v) { */ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { Vec2d v; - _vec3ToVec2(&p, res, &h->face, &v); + _vec3ToHex2d(&p, res, &h->face, &v); _hex2dToCoordIJK(&v, &h->coord); } From 21eace6c957e7d2596464fccc311993bcc488c8c Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:45:41 -0700 Subject: [PATCH 61/91] forward declare --- src/h3lib/lib/faceijk.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index f1bb2b3e28..ae3413d219 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -361,6 +361,23 @@ static const int unitScaleByCIIres[] = { 5764801 // res 16 }; +// Forward declares to make diff nicer +static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); + +/** + * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at + * the specified resolution. + * + * @param p The Vec3d coordinates to encode. + * @param res The desired H3 resolution for the encoding. + * @param h Output: the FaceIJK address of the containing cell. + */ +void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { + Vec2d v; + _vec3ToHex2d(&p, res, &h->face, &v); + _hex2dToCoordIJK(&v, &h->coord); +} + /** * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing the squared euclidean distance to that face center. @@ -466,20 +483,6 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { v->y = r * sin(theta); } -/** - * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at - * the specified resolution. - * - * @param p The Vec3d coordinates to encode. - * @param res The desired H3 resolution for the encoding. - * @param h Output: the FaceIJK address of the containing cell. - */ -void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { - Vec2d v; - _vec3ToHex2d(&p, res, &h->face, &v); - _hex2dToCoordIJK(&v, &h->coord); -} - /** * Determines the 3D coordinates of a point given by 2D hex coordinates * on a particular icosahedral face. From ef61c27694b1fefe9b138e87f33b70759ac47f18 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:47:27 -0700 Subject: [PATCH 62/91] comment --- src/h3lib/lib/faceijk.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index ae3413d219..2d45ec4cf9 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -365,8 +365,8 @@ static const int unitScaleByCIIres[] = { static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); /** - * Encodes a Vec3d coordinate to the FaceIJK address of the containing cell at - * the specified resolution. + * Encodes a Vec3d coordinate to the FaceIJK address of the containing + * cell at the specified resolution. * * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. @@ -374,7 +374,10 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); */ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { Vec2d v; + // first convert to hex2d _vec3ToHex2d(&p, res, &h->face, &v); + + // then convert to ijk+ _hex2dToCoordIJK(&v, &h->coord); } From a4f4e55e2ead57def3c61b11844fc3b4c4ef8c8d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:48:00 -0700 Subject: [PATCH 63/91] bah --- src/h3lib/lib/faceijk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 2d45ec4cf9..2a0df30fbf 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -373,8 +373,8 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); * @param h Output: the FaceIJK address of the containing cell. */ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { - Vec2d v; // first convert to hex2d + Vec2d v; _vec3ToHex2d(&p, res, &h->face, &v); // then convert to ijk+ From 2d83319b15058cb23bb6168e8551a8a9874442f0 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:49:28 -0700 Subject: [PATCH 64/91] bah bah --- src/h3lib/lib/faceijk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 2a0df30fbf..730d87c058 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -370,7 +370,7 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); * * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. - * @param h Output: the FaceIJK address of the containing cell. + * @param h Output: FaceIJK address of the containing cell at resolution res. */ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { // first convert to hex2d From 02bfc81c8502f25f4e3d67adc5e98690b9d2459a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 19:51:28 -0700 Subject: [PATCH 65/91] move around _vec3ToClosestFace --- src/h3lib/lib/faceijk.c | 45 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 730d87c058..a7a961c4b2 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -363,6 +363,7 @@ static const int unitScaleByCIIres[] = { // Forward declares to make diff nicer static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); +static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd); /** * Encodes a Vec3d coordinate to the FaceIJK address of the containing @@ -381,28 +382,6 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { _hex2dToCoordIJK(&v, &h->coord); } -/** - * Encodes a coordinate on the sphere to the corresponding icosahedral face and - * containing the squared euclidean distance to that face center. - * - * @param v3 The Vec3d coordinates to encode. - * @param face Output: the icosahedral face containing the coordinates. - * @param sqd Output: the squared euclidean distance to its face center. - */ -static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd) { - *face = 0; - // The distance between two farthest points is 2.0, therefore the square of - // the distance between two points should always be less or equal than 4.0 . - *sqd = 5.0; - for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(faceCenterPoint[f], *v3); - if (sqdT < *sqd) { - *face = f; - *sqd = sqdT; - } - } -} - /** * Compute the local north and east directions on the tangent plane * at a point on the unit sphere. @@ -996,3 +975,25 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { } while (overage == NEW_FACE); return overage; } + +/** + * Encodes a coordinate on the sphere to the corresponding icosahedral face and + * containing the squared euclidean distance to that face center. + * + * @param v3 The Vec3d coordinates to encode. + * @param face Output: the icosahedral face containing the coordinates. + * @param sqd Output: the squared euclidean distance to its face center. + */ +static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd) { + *face = 0; + // The distance between two farthest points is 2.0, therefore the square of + // the distance between two points should always be less or equal than 4.0 . + *sqd = 5.0; + for (int f = 0; f < NUM_ICOSA_FACES; ++f) { + double sqdT = vec3DistSq(faceCenterPoint[f], *v3); + if (sqdT < *sqd) { + *face = f; + *sqd = sqdT; + } + } +} From 83ea8acce7a343883fe7e661fab03de36467d9e5 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:13:30 -0700 Subject: [PATCH 66/91] todo --- src/h3lib/lib/faceijk.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index a7a961c4b2..e0442bda24 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -362,6 +362,7 @@ static const int unitScaleByCIIres[] = { }; // Forward declares to make diff nicer +// TODO: remove and reorder functions after landing static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd); From 2bca78388cfb167bcd8cf896bd4be59a157f1dc3 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:15:34 -0700 Subject: [PATCH 67/91] bring benchmarks back in --- CMakeLists.txt | 2 + bench.py | 153 ++++++++++++++++++++++++ src/apps/benchmarks/benchmarkCoreApi.c | 156 +++++++++++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 bench.py create mode 100644 src/apps/benchmarks/benchmarkCoreApi.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 257544b737..f379617adc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,7 @@ set(OTHER_SOURCE_FILES src/apps/benchmarks/benchmarkVertex.c src/apps/benchmarks/benchmarkIsValidCell.c src/apps/benchmarks/benchmarkH3Api.c + src/apps/benchmarks/benchmarkCoreApi.c src/apps/benchmarks/benchmarkArea.c) set(ALL_SOURCE_FILES @@ -664,6 +665,7 @@ if(BUILD_BENCHMARKS) endmacro() add_h3_benchmark(benchmarkH3Api src/apps/benchmarks/benchmarkH3Api.c) + add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c) add_h3_benchmark(benchmarkGridDiskCells src/apps/benchmarks/benchmarkGridDiskCells.c) add_h3_benchmark(benchmarkGridPathCells diff --git a/bench.py b/bench.py new file mode 100644 index 0000000000..8a51ba9d59 --- /dev/null +++ b/bench.py @@ -0,0 +1,153 @@ +# /// script +# requires-python = ">=3.10" +# /// + +""" +Benchmark latLngToCell, cellToLatLng, cellToBoundary on two git refs. + +Checks out each ref, injects the benchmark C file if needed, builds, +runs N times, and reports the min. Restores the original branch on exit. + +Usage: + uv run bench.py [ref_a] [ref_b] + +Defaults to master vs vec3d-core. +""" + +import os +import re +import subprocess +import sys + +ROOT = os.path.dirname(os.path.abspath(__file__)) +BUILD = os.path.join(ROOT, "build") +BENCH_SRC = "src/apps/benchmarks/benchmarkCoreApi.c" +BENCH_BIN = os.path.join(BUILD, "bin/benchmarkCoreApi") +CMAKE_ENTRY = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" +N_RUNS = 10 + + +def git(*args): + return subprocess.run( + ["git", *args], capture_output=True, text=True, cwd=ROOT + ) + + +def current_ref(): + r = git("rev-parse", "--abbrev-ref", "HEAD") + branch = r.stdout.strip() + r = git("rev-parse", "--short", "HEAD") + return branch, r.stdout.strip() + + +def checkout(ref, bench_content): + """Switch to ref, injecting benchmark file and CMakeLists entry.""" + # Clean before switching + git("checkout", "--", ".") + src = os.path.join(ROOT, BENCH_SRC) + r = git("ls-files", BENCH_SRC) + if not r.stdout.strip() and os.path.exists(src): + os.remove(src) + + git("checkout", ref) + + # Inject benchmark file + os.makedirs(os.path.dirname(src), exist_ok=True) + with open(src, "w") as f: + f.write(bench_content) + + # Inject CMakeLists entry + cmake = os.path.join(ROOT, "CMakeLists.txt") + txt = open(cmake).read() + if "benchmarkCoreApi" not in txt: + txt = txt.replace( + "add_h3_benchmark(benchmarkH3Api", + CMAKE_ENTRY + "\n add_h3_benchmark(benchmarkH3Api", + ) + open(cmake, "w").write(txt) + + +def build(): + os.makedirs(BUILD, exist_ok=True) + r = subprocess.run( + "cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", + shell=True, capture_output=True, text=True, cwd=BUILD, + ) + if r.returncode != 0: + print(f" BUILD FAILED:\n{r.stderr[-300:]}") + return False + return True + + +def bench(n_runs): + """Run benchmark n_runs times, return {name: min_us}.""" + best = {} + for _ in range(n_runs): + r = subprocess.run([BENCH_BIN], capture_output=True, text=True) + for line in r.stdout.splitlines(): + m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) + if m: + name, us = m.group(1), float(m.group(2)) + if name not in best or us < best[name]: + best[name] = us + return best + + +def bench_ref(ref, bench_content): + """Checkout ref, build, benchmark. Returns {name: min_us} or None.""" + print(f"\n{'='*50}") + print(f"Benchmarking: {ref}") + print(f"{'='*50}") + + checkout(ref, bench_content) + branch, sha = current_ref() + print(f" branch: {branch} commit: {sha}") + + if not build(): + return None + + print(f" Running {N_RUNS} iterations...") + results = bench(N_RUNS) + for name, us in results.items(): + print(f" {name}: {us:.4f} us/call") + return results + + +def main(): + ref_a = sys.argv[1] if len(sys.argv) > 1 else "master" + ref_b = sys.argv[2] if len(sys.argv) > 2 else "vec3d-core" + + original, _ = current_ref() + src = os.path.join(ROOT, BENCH_SRC) + if not os.path.exists(src): + print(f"Error: {BENCH_SRC} not found.") + return + bench_content = open(src).read() + + results = {} + try: + for ref in [ref_a, ref_b]: + r = bench_ref(ref, bench_content) + if r: + results[ref] = r + finally: + checkout(original, bench_content) + print(f"\nRestored: {original}") + + if len(results) == 2: + print(f"\n{'='*50}") + print(f"Comparison (min of {N_RUNS} runs)") + print(f"{'='*50}") + print(f"{'Function':<20} {ref_a:>12} {ref_b:>12} {'Change':>10}") + print(f"{'-'*20} {'-'*12} {'-'*12} {'-'*10}") + for name in results[ref_a]: + a = results[ref_a][name] + b = results[ref_b].get(name) + if b is not None: + pct = (b - a) / a * 100 + sign = "+" if pct > 0 else "" + print(f"{name:<20} {a:>10.4f}us {b:>10.4f}us {sign}{pct:>8.1f}%") + + +if __name__ == "__main__": + main() diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c new file mode 100644 index 0000000000..dbf05363e9 --- /dev/null +++ b/src/apps/benchmarks/benchmarkCoreApi.c @@ -0,0 +1,156 @@ +/* + * Copyright 2026 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 benchmarkCoreApi.c + * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary, + * directedEdgeToBoundary with diverse inputs. + * + * Uses START_TIMER/END_TIMER from benchmark.h for cross-platform timing, + * but reports per-call microseconds (not per-iteration) for use with + * bench.py. + */ + +#include + +#include "benchmark.h" +#include "h3api.h" + +#define N_POINTS 20 +#define N_RESOLUTIONS 4 +#define ITERATIONS 50000 + +// Points spread across the globe (hitting different icosahedron faces) +static const LatLng points[N_POINTS] = { + {0.659966917655, -2.1364398519396}, {0.8527087756, -0.0405865662}, + {0.6234025842, 2.0075945568}, {-0.5934119457, 2.5368879644}, + {0.4799655443, 0.6457718232}, {-0.4014257280, -0.7610418886}, + {0.9679776674, -1.7453292520}, {-1.2217304764, 0.0000000000}, + {1.2217304764, 0.0000000000}, {0.0000000000, 0.0000000000}, + {0.0000000000, 3.1415926536}, {0.7853981634, 1.5707963268}, + {-0.7853981634, -1.5707963268}, {0.3490658504, -1.2217304764}, + {-0.1745329252, 0.5235987756}, {1.0471975512, -0.5235987756}, + {-1.0471975512, 2.0943951024}, {0.2617993878, 1.8325957146}, + {-0.8726646260, -1.0471975512}, {0.5235987756, -2.6179938780}, +}; + +static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; + +int main(void) { + H3Index cells[N_POINTS * N_RESOLUTIONS]; + int nCells = 0; + + // Pre-compute cells for inverse benchmarks + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); + nCells++; + } + } + + // Pre-compute directed edges and vertices + H3Index edges[N_POINTS * N_RESOLUTIONS]; + H3Index verts[N_POINTS * N_RESOLUTIONS]; + int nEdges = 0; + int nVerts = 0; + for (int c = 0; c < nCells; c++) { + H3Index edgeOut[6]; + H3_EXPORT(originToDirectedEdges)(cells[c], edgeOut); + edges[nEdges++] = edgeOut[0]; + H3Index vertOut[6]; + H3_EXPORT(cellToVertexes)(cells[c], vertOut); + verts[nVerts++] = vertOut[0]; + } + + // Benchmark latLngToCell + { + H3Index h; + int totalCalls = ITERATIONS * N_POINTS * N_RESOLUTIONS; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int r = 0; r < N_RESOLUTIONS; r++) { + for (int p = 0; p < N_POINTS; p++) { + H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); + } + } + } + END_TIMER(duration); + printf("latLngToCell: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + // Benchmark cellToLatLng + { + LatLng out; + int totalCalls = ITERATIONS * nCells; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToLatLng)(cells[c], &out); + } + } + END_TIMER(duration); + printf("cellToLatLng: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + // Benchmark cellToBoundary + { + CellBoundary cb; + int totalCalls = ITERATIONS * nCells; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int c = 0; c < nCells; c++) { + H3_EXPORT(cellToBoundary)(cells[c], &cb); + } + } + END_TIMER(duration); + printf("cellToBoundary: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + // Benchmark directedEdgeToBoundary + { + CellBoundary cb; + int totalCalls = ITERATIONS * nEdges; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int e = 0; e < nEdges; e++) { + H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); + } + } + END_TIMER(duration); + printf("directedEdgeToBoundary: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + // Benchmark vertexToLatLng + { + LatLng out; + int totalCalls = ITERATIONS * nVerts; + START_TIMER; + for (int iter = 0; iter < ITERATIONS; iter++) { + for (int v = 0; v < nVerts; v++) { + H3_EXPORT(vertexToLatLng)(verts[v], &out); + } + } + END_TIMER(duration); + printf("vertexToLatLng: %.4Lf us/call (%d calls)\n", + duration / totalCalls, totalCalls); + } + + return 0; +} From 80fa000d11b0281e86da8cc73e09997bdb0cdf25 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:24:22 -0700 Subject: [PATCH 68/91] nits --- src/h3lib/lib/faceijk.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index e0442bda24..c9802a264a 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -525,12 +525,12 @@ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { * * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. - * @param v3 Output: the 3D coordinates of the cell center point. + * @param v3 Output: The 3D coordinates of the cell center point. */ -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3) { +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *g) { Vec2d v; _ijkToHex2d(&h->coord, &v); - _hex2dToVec3(&v, h->face, res, 0, v3); + _hex2dToVec3(&v, h->face, res, 0, g); } /** @@ -541,7 +541,7 @@ void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3) { * @param res The H3 resolution of the cell. * @param start The first topological vertex to return. * @param length The number of topological vertexes to return. - * @param g Output: the spherical coordinates of the cell boundary. + * @param g Output: The spherical coordinates of the cell boundary. */ void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g) { From 6a4f5943a0448cae455cdd31f3bfd18f1c1aef29 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:28:15 -0700 Subject: [PATCH 69/91] pass by value --- src/h3lib/lib/faceijk.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index c9802a264a..b6ebe585ef 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -394,7 +394,8 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { * @param north Output: local north direction on tangent plane. * @param east Output: local east direction on tangent plane. */ -static void _vec3TangentBasis(const Vec3d *p, Vec3d *north, Vec3d *east) { +static inline void _vec3TangentBasis(const Vec3d *p, Vec3d *north, + Vec3d *east) { Vec3d northPole = {0.0, 0.0, 1.0}; double NdotP = vec3Dot(northPole, *p); *north = vec3LinComb(1.0, northPole, -NdotP, *p); @@ -408,13 +409,13 @@ static void _vec3TangentBasis(const Vec3d *p, Vec3d *north, Vec3d *east) { * @param p2 The second vector. * @return The azimuth in radians. */ -static double _vec3AzimuthRads(const Vec3d *p1, const Vec3d *p2) { +static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { Vec3d northDir, eastDir; - _vec3TangentBasis(p1, &northDir, &eastDir); + _vec3TangentBasis(&p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 - double p2dotp1 = vec3Dot(*p2, *p1); - Vec3d p2_on_tangent = vec3LinComb(1.0, *p2, -p2dotp1, *p1); + double p2dotp1 = vec3Dot(p2, p1); + Vec3d p2_on_tangent = vec3LinComb(1.0, p2, -p2dotp1, p1); vec3Normalize(&p2_on_tangent); return atan2(vec3Dot(p2_on_tangent, eastDir), @@ -444,7 +445,7 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { } // now have face and r, now find CCW theta from CII i-axis - double p_az = _vec3AzimuthRads(&faceCenterPoint[*face], p); + double p_az = _vec3AzimuthRads(faceCenterPoint[*face], *p); double theta = _posAngleRads(faceAxesAzRadsCII[*face][0] - _posAngleRads(p_az)); From 2dbd994352eb927c52ceae19d2efcce0b6685e7a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:44:08 -0700 Subject: [PATCH 70/91] expression --- src/h3lib/lib/faceijk.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index b6ebe585ef..5f73ab43d2 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -397,8 +397,7 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { static inline void _vec3TangentBasis(const Vec3d *p, Vec3d *north, Vec3d *east) { Vec3d northPole = {0.0, 0.0, 1.0}; - double NdotP = vec3Dot(northPole, *p); - *north = vec3LinComb(1.0, northPole, -NdotP, *p); + *north = vec3LinComb(1.0, northPole, -vec3Dot(northPole, *p), *p); vec3Normalize(north); *east = vec3Cross(*north, *p); } @@ -414,8 +413,7 @@ static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { _vec3TangentBasis(&p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 - double p2dotp1 = vec3Dot(p2, p1); - Vec3d p2_on_tangent = vec3LinComb(1.0, p2, -p2dotp1, p1); + Vec3d p2_on_tangent = vec3LinComb(1.0, p2, -vec3Dot(p2, p1), p1); vec3Normalize(&p2_on_tangent); return atan2(vec3Dot(p2_on_tangent, eastDir), From 9fe28281f6334733658d9af20121028b049b6e15 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:46:34 -0700 Subject: [PATCH 71/91] _vec3TangentBasis --- src/h3lib/lib/faceijk.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 5f73ab43d2..aafe75e097 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -394,12 +394,11 @@ void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h) { * @param north Output: local north direction on tangent plane. * @param east Output: local east direction on tangent plane. */ -static inline void _vec3TangentBasis(const Vec3d *p, Vec3d *north, - Vec3d *east) { +static inline void _vec3TangentBasis(Vec3d p, Vec3d *north, Vec3d *east) { Vec3d northPole = {0.0, 0.0, 1.0}; - *north = vec3LinComb(1.0, northPole, -vec3Dot(northPole, *p), *p); + *north = vec3LinComb(1.0, northPole, -vec3Dot(northPole, p), p); vec3Normalize(north); - *east = vec3Cross(*north, *p); + *east = vec3Cross(*north, p); } /** @@ -410,7 +409,7 @@ static inline void _vec3TangentBasis(const Vec3d *p, Vec3d *north, */ static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { Vec3d northDir, eastDir; - _vec3TangentBasis(&p1, &northDir, &eastDir); + _vec3TangentBasis(p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 Vec3d p2_on_tangent = vec3LinComb(1.0, p2, -vec3Dot(p2, p1), p1); @@ -508,13 +507,12 @@ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); // now find the point at (r,theta) from the face center - const Vec3d *center = &faceCenterPoint[face]; Vec3d northDir, eastDir; - _vec3TangentBasis(center, &northDir, &eastDir); + _vec3TangentBasis(faceCenterPoint[face], &northDir, &eastDir); Vec3d dir = vec3LinComb(cos(theta), northDir, sin(theta), eastDir); - *v3 = vec3LinComb(cos(r), *center, sin(r), dir); + *v3 = vec3LinComb(cos(r), faceCenterPoint[face], sin(r), dir); vec3Normalize(v3); } From 2d48ca0df4b7001552a88e82db132e5ebd31de3f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 22:49:50 -0700 Subject: [PATCH 72/91] nits --- src/h3lib/lib/faceijk.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index aafe75e097..6aed975c32 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -713,7 +713,7 @@ void _faceIjkPentToVerts(FaceIJK *fijk, int *res, FaceIJK *fijkVerts) { * @param res The H3 resolution of the cell. * @param start The first topological vertex to return. * @param length The number of topological vertexes to return. - * @param g Output: the spherical coordinates of the cell boundary. + * @param g Output: The spherical coordinates of the cell boundary. */ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g) { @@ -752,7 +752,7 @@ void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, */ if (isResolutionClassIII(res) && vert > start && fijk.face != lastFace && lastOverage != FACE_EDGE) { - // find Vec2d of the two vertexes on original face + // find hex2d of the two vertexes on original face int lastV = (v + 5) % NUM_HEX_VERTS; Vec2d orig2d0; _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); @@ -979,8 +979,8 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { * containing the squared euclidean distance to that face center. * * @param v3 The Vec3d coordinates to encode. - * @param face Output: the icosahedral face containing the coordinates. - * @param sqd Output: the squared euclidean distance to its face center. + * @param face Output: The icosahedral face containing the coordinates. + * @param sqd Output: The squared euclidean distance to its face center. */ static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd) { *face = 0; From b2232b6ac93d26f634309ad172cb4e91f31cd8b6 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 23:00:04 -0700 Subject: [PATCH 73/91] boop --- src/h3lib/lib/faceijk.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 6aed975c32..a9b8727a95 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -442,9 +442,9 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { } // now have face and r, now find CCW theta from CII i-axis - double p_az = _vec3AzimuthRads(faceCenterPoint[*face], *p); - double theta = - _posAngleRads(faceAxesAzRadsCII[*face][0] - _posAngleRads(p_az)); + double theta = _posAngleRads( + faceAxesAzRadsCII[*face][0] - + _posAngleRads(_vec3AzimuthRads(faceCenterPoint[*face], *p))); // adjust theta for Class III (odd resolutions) if (isResolutionClassIII(res)) From fd2a985208619ee78279cbb6bdd58ac27ee5da96 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 23:01:29 -0700 Subject: [PATCH 74/91] i swear... --- src/h3lib/lib/faceijk.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index a9b8727a95..20a26e3bc2 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -425,8 +425,8 @@ static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { * * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. - * @param face Output: the icosahedral face containing the coordinates. - * @param v Output: the 2D hex coordinates of the cell containing the point. + * @param face Output: The icosahedral face containing the coordinates. + * @param v Output: The 2D hex coordinates of the cell containing the point. */ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { // determine the icosahedron face @@ -457,7 +457,7 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { r *= INV_RES0_U_GNOMONIC; for (int i = 0; i < res; i++) r *= M_SQRT7; - // we now have (r, theta) in Vec2d with theta ccw from x-axes + // we now have (r, theta) in hex2d with theta ccw from x-axes // convert to local x,y v->x = r * cos(theta); From 7d568f0156e2c6faaaaa58994851bf63978c37ca Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Tue, 31 Mar 2026 23:04:47 -0700 Subject: [PATCH 75/91] boop --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f379617adc..6788d3166a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -169,12 +169,12 @@ set(LIB_SOURCE_FILES src/h3lib/include/constants.h src/h3lib/include/coordijk.h src/h3lib/include/algos.h - src/h3lib/lib/coordijk.c src/h3lib/include/adder.h src/h3lib/include/area.h src/h3lib/include/cellsToMultiPoly.h src/h3lib/lib/h3Assert.c src/h3lib/lib/algos.c + src/h3lib/lib/coordijk.c src/h3lib/lib/bbox.c src/h3lib/lib/polygon.c src/h3lib/lib/polyfill.c From a2ce573639552f5f8fb5ffc05e611a39ce6cbb0a Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 09:56:22 -0700 Subject: [PATCH 76/91] 9 digits --- src/apps/filters/h3.c | 2 +- tests/cli/edgeLengthM.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/filters/h3.c b/src/apps/filters/h3.c index 0fa10e6e59..7ad555251c 100644 --- a/src/apps/filters/h3.c +++ b/src/apps/filters/h3.c @@ -2642,7 +2642,7 @@ SUBCOMMAND(edgeLengthM, if (err) { return err; } - printf("%.6lf\n", length); + printf("%.9lf\n", length); return E_SUCCESS; } diff --git a/tests/cli/edgeLengthM.txt b/tests/cli/edgeLengthM.txt index 0dd238a9b2..3575bb9f3c 100644 --- a/tests/cli/edgeLengthM.txt +++ b/tests/cli/edgeLengthM.txt @@ -1,4 +1,4 @@ add_h3_cli_test(testCliEdgeLengthM "edgeLengthM -c 115283473fffffff" - "10294.736086") + "10294.736086200") add_h3_cli_test(testCliNotEdgeLengthM "edgeLengthM -c 85283473fffffff 2>&1" "Error 6: Directed edge argument was not valid") From 218c973226bb0b4952712f0741bb2ead590323c2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 10:04:18 -0700 Subject: [PATCH 77/91] move --- src/h3lib/lib/h3Index.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index f8bee7f738..9c66ccbc16 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1167,35 +1167,35 @@ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { } /** - * Determines the spherical coordinates of the center point of an H3 index. + * Determines the 3D cartesian coordinates of the center of an H3 cell. * * @param h3 The H3 index. - * @param g The spherical coordinates of the H3 cell center. + * @param v The 3D cartesian coordinates of the H3 cell center. + * @return E_SUCCESS on success, or another H3Error code on failure. */ -H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { - Vec3d v; - H3Error e = cellToVec3(h3, &v); +H3Error cellToVec3(H3Index h3, Vec3d *v) { + FaceIJK fijk; + H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { return e; } - *g = vec3ToLatLng(v); + _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3), v); return E_SUCCESS; } /** - * Determines the 3D cartesian coordinates of the center of an H3 cell. + * Determines the spherical coordinates of the center point of an H3 index. * * @param h3 The H3 index. - * @param v The 3D cartesian coordinates of the H3 cell center. - * @return E_SUCCESS on success, or another H3Error code on failure. + * @param g The spherical coordinates of the H3 cell center. */ -H3Error cellToVec3(H3Index h3, Vec3d *v) { - FaceIJK fijk; - H3Error e = _h3ToFaceIjk(h3, &fijk); +H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { + Vec3d v; + H3Error e = cellToVec3(h3, &v); if (e) { return e; } - _faceIjkToVec3(&fijk, H3_GET_RESOLUTION(h3), v); + *g = vec3ToLatLng(v); return E_SUCCESS; } From e63e3131996c96c41f60e867d2947671698a34ed Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 10:09:43 -0700 Subject: [PATCH 78/91] 8 digits --- src/apps/filters/h3.c | 2 +- tests/cli/edgeLengthM.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/filters/h3.c b/src/apps/filters/h3.c index 7ad555251c..39fbf70393 100644 --- a/src/apps/filters/h3.c +++ b/src/apps/filters/h3.c @@ -2642,7 +2642,7 @@ SUBCOMMAND(edgeLengthM, if (err) { return err; } - printf("%.9lf\n", length); + printf("%.8lf\n", length); return E_SUCCESS; } diff --git a/tests/cli/edgeLengthM.txt b/tests/cli/edgeLengthM.txt index 3575bb9f3c..74ff16260d 100644 --- a/tests/cli/edgeLengthM.txt +++ b/tests/cli/edgeLengthM.txt @@ -1,4 +1,4 @@ add_h3_cli_test(testCliEdgeLengthM "edgeLengthM -c 115283473fffffff" - "10294.736086200") + "10294.73608620") add_h3_cli_test(testCliNotEdgeLengthM "edgeLengthM -c 85283473fffffff 2>&1" "Error 6: Directed edge argument was not valid") From 4dc91f739a295686d4f74132430beb380142ec1d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 10:19:49 -0700 Subject: [PATCH 79/91] comments align --- src/h3lib/lib/faceijk.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 20a26e3bc2..4d0203379c 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -465,16 +465,19 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { } /** - * Determines the 3D coordinates of a point given by 2D hex coordinates - * on a particular icosahedral face. + * Determines the 3D coordinates of a cell given by 2D + * hex coordinates on a particular icosahedral face. * - * @param v The 2D hex coordinates of the point. - * @param face The 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 Whether this is a substrate grid. - * @param v3 Output: the 3D coordinates of the point. + * @param substrate Indicates whether or not this grid is actually a substrate + * grid relative to the specified resolution. + * @param v3 Output: the 3D coordinates of the cell center point */ void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { + // calculate (r, theta) in hex2d double r = _v2dMag(v); if (r < EPSILON) { From bd5c9a540fe05f001e40fae7940cc109a4091a96 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 11:22:13 -0700 Subject: [PATCH 80/91] better names for vec3LinComb --- src/h3lib/include/vec3d.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index 7304227a2d..ec0f09235b 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -57,11 +57,11 @@ static inline LatLng vec3ToLatLng(Vec3d v) { }; } -static inline Vec3d vec3LinComb(double s1, Vec3d a, double s2, Vec3d b) { +static inline Vec3d vec3LinComb(double a, Vec3d v1, double b, Vec3d v2) { return (Vec3d){ - s1 * a.x + s2 * b.x, - s1 * a.y + s2 * b.y, - s1 * a.z + s2 * b.z, + a * v1.x + b * v2.x, + a * v1.y + b * v2.y, + a * v1.z + b * v2.z, }; } From 9fcb5aaf0e94351cfd00d4984eff4e9533d6c3af Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 12:01:17 -0700 Subject: [PATCH 81/91] dead code: remove faceCenterGeo definition --- src/h3lib/include/faceijk.h | 2 -- src/h3lib/lib/faceijk.c | 24 ------------------------ 2 files changed, 26 deletions(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 450d81dc3e..8c20e9ba4d 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -48,8 +48,6 @@ typedef struct { /// face } FaceOrientIJK; -extern const LatLng faceCenterGeo[NUM_ICOSA_FACES]; - // indexes for faceNeighbors table /** IJ quadrant faceNeighbors table direction */ #define IJ 1 diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 4d0203379c..000313a28c 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -36,30 +36,6 @@ #define M_SQRT7 2.6457513110645905905016157536392604257102 #define M_RSQRT7 0.37796447300922722721451653623418006081576 -/** @brief icosahedron face centers in lat/lng radians */ -const LatLng faceCenterGeo[NUM_ICOSA_FACES] = { - {0.803582649718989942, 1.248397419617396099}, // face 0 - {1.307747883455638156, 2.536945009877921159}, // face 1 - {1.054751253523952054, -1.347517358900396623}, // face 2 - {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 From a06d1687b1a4fce286a769c9965bef82fb19e507 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 1 Apr 2026 12:07:03 -0700 Subject: [PATCH 82/91] remove benchmarks and justfile --- CMakeLists.txt | 2 - bench.py | 153 ------------------------ justfile | 64 ---------- src/apps/benchmarks/benchmarkCoreApi.c | 156 ------------------------- 4 files changed, 375 deletions(-) delete mode 100644 bench.py delete mode 100644 justfile delete mode 100644 src/apps/benchmarks/benchmarkCoreApi.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6788d3166a..6a9ad24594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,7 +324,6 @@ set(OTHER_SOURCE_FILES src/apps/benchmarks/benchmarkVertex.c src/apps/benchmarks/benchmarkIsValidCell.c src/apps/benchmarks/benchmarkH3Api.c - src/apps/benchmarks/benchmarkCoreApi.c src/apps/benchmarks/benchmarkArea.c) set(ALL_SOURCE_FILES @@ -665,7 +664,6 @@ if(BUILD_BENCHMARKS) endmacro() add_h3_benchmark(benchmarkH3Api src/apps/benchmarks/benchmarkH3Api.c) - add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c) add_h3_benchmark(benchmarkGridDiskCells src/apps/benchmarks/benchmarkGridDiskCells.c) add_h3_benchmark(benchmarkGridPathCells diff --git a/bench.py b/bench.py deleted file mode 100644 index 8a51ba9d59..0000000000 --- a/bench.py +++ /dev/null @@ -1,153 +0,0 @@ -# /// script -# requires-python = ">=3.10" -# /// - -""" -Benchmark latLngToCell, cellToLatLng, cellToBoundary on two git refs. - -Checks out each ref, injects the benchmark C file if needed, builds, -runs N times, and reports the min. Restores the original branch on exit. - -Usage: - uv run bench.py [ref_a] [ref_b] - -Defaults to master vs vec3d-core. -""" - -import os -import re -import subprocess -import sys - -ROOT = os.path.dirname(os.path.abspath(__file__)) -BUILD = os.path.join(ROOT, "build") -BENCH_SRC = "src/apps/benchmarks/benchmarkCoreApi.c" -BENCH_BIN = os.path.join(BUILD, "bin/benchmarkCoreApi") -CMAKE_ENTRY = " add_h3_benchmark(benchmarkCoreApi src/apps/benchmarks/benchmarkCoreApi.c)" -N_RUNS = 10 - - -def git(*args): - return subprocess.run( - ["git", *args], capture_output=True, text=True, cwd=ROOT - ) - - -def current_ref(): - r = git("rev-parse", "--abbrev-ref", "HEAD") - branch = r.stdout.strip() - r = git("rev-parse", "--short", "HEAD") - return branch, r.stdout.strip() - - -def checkout(ref, bench_content): - """Switch to ref, injecting benchmark file and CMakeLists entry.""" - # Clean before switching - git("checkout", "--", ".") - src = os.path.join(ROOT, BENCH_SRC) - r = git("ls-files", BENCH_SRC) - if not r.stdout.strip() and os.path.exists(src): - os.remove(src) - - git("checkout", ref) - - # Inject benchmark file - os.makedirs(os.path.dirname(src), exist_ok=True) - with open(src, "w") as f: - f.write(bench_content) - - # Inject CMakeLists entry - cmake = os.path.join(ROOT, "CMakeLists.txt") - txt = open(cmake).read() - if "benchmarkCoreApi" not in txt: - txt = txt.replace( - "add_h3_benchmark(benchmarkH3Api", - CMAKE_ENTRY + "\n add_h3_benchmark(benchmarkH3Api", - ) - open(cmake, "w").write(txt) - - -def build(): - os.makedirs(BUILD, exist_ok=True) - r = subprocess.run( - "cmake -DCMAKE_BUILD_TYPE=Release .. && make benchmarkCoreApi", - shell=True, capture_output=True, text=True, cwd=BUILD, - ) - if r.returncode != 0: - print(f" BUILD FAILED:\n{r.stderr[-300:]}") - return False - return True - - -def bench(n_runs): - """Run benchmark n_runs times, return {name: min_us}.""" - best = {} - for _ in range(n_runs): - r = subprocess.run([BENCH_BIN], capture_output=True, text=True) - for line in r.stdout.splitlines(): - m = re.match(r"(\w+):\s+([\d.]+)\s+us/call", line) - if m: - name, us = m.group(1), float(m.group(2)) - if name not in best or us < best[name]: - best[name] = us - return best - - -def bench_ref(ref, bench_content): - """Checkout ref, build, benchmark. Returns {name: min_us} or None.""" - print(f"\n{'='*50}") - print(f"Benchmarking: {ref}") - print(f"{'='*50}") - - checkout(ref, bench_content) - branch, sha = current_ref() - print(f" branch: {branch} commit: {sha}") - - if not build(): - return None - - print(f" Running {N_RUNS} iterations...") - results = bench(N_RUNS) - for name, us in results.items(): - print(f" {name}: {us:.4f} us/call") - return results - - -def main(): - ref_a = sys.argv[1] if len(sys.argv) > 1 else "master" - ref_b = sys.argv[2] if len(sys.argv) > 2 else "vec3d-core" - - original, _ = current_ref() - src = os.path.join(ROOT, BENCH_SRC) - if not os.path.exists(src): - print(f"Error: {BENCH_SRC} not found.") - return - bench_content = open(src).read() - - results = {} - try: - for ref in [ref_a, ref_b]: - r = bench_ref(ref, bench_content) - if r: - results[ref] = r - finally: - checkout(original, bench_content) - print(f"\nRestored: {original}") - - if len(results) == 2: - print(f"\n{'='*50}") - print(f"Comparison (min of {N_RUNS} runs)") - print(f"{'='*50}") - print(f"{'Function':<20} {ref_a:>12} {ref_b:>12} {'Change':>10}") - print(f"{'-'*20} {'-'*12} {'-'*12} {'-'*10}") - for name in results[ref_a]: - a = results[ref_a][name] - b = results[ref_b].get(name) - if b is not None: - pct = (b - a) / a * 100 - sign = "+" if pct > 0 else "" - print(f"{name:<20} {a:>10.4f}us {b:>10.4f}us {sign}{pct:>8.1f}%") - - -if __name__ == "__main__": - main() diff --git a/justfile b/justfile deleted file mode 100644 index b869643589..0000000000 --- a/justfile +++ /dev/null @@ -1,64 +0,0 @@ -init: purge - mkdir build - -build: - cd build; cmake -DCMAKE_BUILD_TYPE=Release ..; make - -purge: - rm -rf build - rm -rf *.trace - rm -rf .ipynb_checkpoints - rm -rf .cache - rm -rf .claude - -test-fast: build - cd build; make test-fast - -test-slow: build - cd build; make test - -# Run a single test binary. Dots (progress) go to /dev/null; failures print to stderr. -test-one TEST: build - ./build/bin/{{TEST}} > /dev/null - -test: - just test-fast - -bench: build - # ./build/bin/benchmarkCellsToPolyAlgos - ./build/bin/benchmarkGosperIter - -coverage: purge - mkdir build - cd build; cmake -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON ..; make; make coverage - open build/coverage/index.html - -# Show uncovered lines/branches for a source file from the lcov .info data. -# Run `just coverage` first. Example: just coverage-gaps linkedGeo.c -coverage-gaps FILE: - #!/usr/bin/env bash - set -e - info="build/coverage.cleaned.info" - if [ ! -f "$info" ]; then - echo "No coverage data found — run 'just coverage' first." - exit 1 - fi - # Extract the section for this file - section=$(sed -n "/SF:.*\/{{FILE}}$/,/end_of_record/p" "$info") - if [ -z "$section" ]; then - echo "File {{FILE}} not found in coverage data." - echo "Available files:" - grep '^SF:' "$info" | sed 's|.*src/h3lib/||' - exit 1 - fi - echo "=== Summary ===" - lf=$(echo "$section" | grep '^LF:' | cut -d: -f2) - lh=$(echo "$section" | grep '^LH:' | cut -d: -f2) - brf=$(echo "$section" | grep '^BRF:' | cut -d: -f2) - brh=$(echo "$section" | grep '^BRH:' | cut -d: -f2) - echo "Lines: $lh/$lf Branches: $brh/$brf" - echo "" - echo "=== Uncovered lines (DA:line,0) ===" - echo "$section" | grep '^DA:' | awk -F'[,:]' '$3 == 0 {print " line " $2}' || echo " (none)" - echo "" - echo "=== Untaken branches (BRDA:line,block,branch,0) ===" diff --git a/src/apps/benchmarks/benchmarkCoreApi.c b/src/apps/benchmarks/benchmarkCoreApi.c deleted file mode 100644 index dbf05363e9..0000000000 --- a/src/apps/benchmarks/benchmarkCoreApi.c +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2026 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 benchmarkCoreApi.c - * @brief Benchmark latLngToCell, cellToLatLng, cellToBoundary, - * directedEdgeToBoundary with diverse inputs. - * - * Uses START_TIMER/END_TIMER from benchmark.h for cross-platform timing, - * but reports per-call microseconds (not per-iteration) for use with - * bench.py. - */ - -#include - -#include "benchmark.h" -#include "h3api.h" - -#define N_POINTS 20 -#define N_RESOLUTIONS 4 -#define ITERATIONS 50000 - -// Points spread across the globe (hitting different icosahedron faces) -static const LatLng points[N_POINTS] = { - {0.659966917655, -2.1364398519396}, {0.8527087756, -0.0405865662}, - {0.6234025842, 2.0075945568}, {-0.5934119457, 2.5368879644}, - {0.4799655443, 0.6457718232}, {-0.4014257280, -0.7610418886}, - {0.9679776674, -1.7453292520}, {-1.2217304764, 0.0000000000}, - {1.2217304764, 0.0000000000}, {0.0000000000, 0.0000000000}, - {0.0000000000, 3.1415926536}, {0.7853981634, 1.5707963268}, - {-0.7853981634, -1.5707963268}, {0.3490658504, -1.2217304764}, - {-0.1745329252, 0.5235987756}, {1.0471975512, -0.5235987756}, - {-1.0471975512, 2.0943951024}, {0.2617993878, 1.8325957146}, - {-0.8726646260, -1.0471975512}, {0.5235987756, -2.6179938780}, -}; - -static const int resolutions[N_RESOLUTIONS] = {0, 5, 9, 15}; - -int main(void) { - H3Index cells[N_POINTS * N_RESOLUTIONS]; - int nCells = 0; - - // Pre-compute cells for inverse benchmarks - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &cells[nCells]); - nCells++; - } - } - - // Pre-compute directed edges and vertices - H3Index edges[N_POINTS * N_RESOLUTIONS]; - H3Index verts[N_POINTS * N_RESOLUTIONS]; - int nEdges = 0; - int nVerts = 0; - for (int c = 0; c < nCells; c++) { - H3Index edgeOut[6]; - H3_EXPORT(originToDirectedEdges)(cells[c], edgeOut); - edges[nEdges++] = edgeOut[0]; - H3Index vertOut[6]; - H3_EXPORT(cellToVertexes)(cells[c], vertOut); - verts[nVerts++] = vertOut[0]; - } - - // Benchmark latLngToCell - { - H3Index h; - int totalCalls = ITERATIONS * N_POINTS * N_RESOLUTIONS; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int r = 0; r < N_RESOLUTIONS; r++) { - for (int p = 0; p < N_POINTS; p++) { - H3_EXPORT(latLngToCell)(&points[p], resolutions[r], &h); - } - } - } - END_TIMER(duration); - printf("latLngToCell: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark cellToLatLng - { - LatLng out; - int totalCalls = ITERATIONS * nCells; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToLatLng)(cells[c], &out); - } - } - END_TIMER(duration); - printf("cellToLatLng: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark cellToBoundary - { - CellBoundary cb; - int totalCalls = ITERATIONS * nCells; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int c = 0; c < nCells; c++) { - H3_EXPORT(cellToBoundary)(cells[c], &cb); - } - } - END_TIMER(duration); - printf("cellToBoundary: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark directedEdgeToBoundary - { - CellBoundary cb; - int totalCalls = ITERATIONS * nEdges; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int e = 0; e < nEdges; e++) { - H3_EXPORT(directedEdgeToBoundary)(edges[e], &cb); - } - } - END_TIMER(duration); - printf("directedEdgeToBoundary: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - // Benchmark vertexToLatLng - { - LatLng out; - int totalCalls = ITERATIONS * nVerts; - START_TIMER; - for (int iter = 0; iter < ITERATIONS; iter++) { - for (int v = 0; v < nVerts; v++) { - H3_EXPORT(vertexToLatLng)(verts[v], &out); - } - } - END_TIMER(duration); - printf("vertexToLatLng: %.4Lf us/call (%d calls)\n", - duration / totalCalls, totalCalls); - } - - return 0; -} From 1aa5d4a20bc87ff64807b6da527ca989f2f678ea Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 6 Apr 2026 11:37:09 -0700 Subject: [PATCH 83/91] camel_case --- src/h3lib/lib/faceijk.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 000313a28c..59c71f1848 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -388,11 +388,10 @@ static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { _vec3TangentBasis(p1, &northDir, &eastDir); // project p2 onto tangent plane at p1 - Vec3d p2_on_tangent = vec3LinComb(1.0, p2, -vec3Dot(p2, p1), p1); - vec3Normalize(&p2_on_tangent); + Vec3d p2Proj = vec3LinComb(1.0, p2, -vec3Dot(p2, p1), p1); + vec3Normalize(&p2Proj); - return atan2(vec3Dot(p2_on_tangent, eastDir), - vec3Dot(p2_on_tangent, northDir)); + return atan2(vec3Dot(p2Proj, eastDir), vec3Dot(p2Proj, northDir)); } /** From c7392845990bda5377d2af3fb6b1b74ff1bb1c02 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Mon, 6 Apr 2026 11:45:34 -0700 Subject: [PATCH 84/91] fix more snakeCase --- src/apps/testapps/testVec3.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index cd38bca4a5..e89727cb5c 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -83,13 +83,13 @@ SUITE(Vec3d) { TEST(vec3ToCell_nonFinite) { H3Index out; - Vec3d nan_x = {.x = NAN, .y = 0.0, .z = 0.0}; - t_assert(vec3ToCell(&nan_x, 0, &out) == E_DOMAIN, "NaN x is rejected"); - Vec3d inf_y = {.x = 0.0, .y = INFINITY, .z = 0.0}; - t_assert(vec3ToCell(&inf_y, 0, &out) == E_DOMAIN, + Vec3d nanX = {.x = NAN, .y = 0.0, .z = 0.0}; + t_assert(vec3ToCell(&nanX, 0, &out) == E_DOMAIN, "NaN x is rejected"); + Vec3d infY = {.x = 0.0, .y = INFINITY, .z = 0.0}; + t_assert(vec3ToCell(&infY, 0, &out) == E_DOMAIN, "infinite y is rejected"); - Vec3d inf_z = {.x = 0.0, .y = 0.0, .z = -INFINITY}; - t_assert(vec3ToCell(&inf_z, 0, &out) == E_DOMAIN, + Vec3d infZ = {.x = 0.0, .y = 0.0, .z = -INFINITY}; + t_assert(vec3ToCell(&infZ, 0, &out) == E_DOMAIN, "infinite z is rejected"); } } From 5cffa92aa887d47bb97cc6bdcc4917bd44fbdcf4 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 8 Apr 2026 23:56:54 -0700 Subject: [PATCH 85/91] small norm handling --- src/apps/testapps/testVec3dInternal.c | 24 ++++++++++++++++++++++++ src/h3lib/include/vec3d.h | 18 +++++++++++++----- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/apps/testapps/testVec3dInternal.c b/src/apps/testapps/testVec3dInternal.c index 58e0080da0..f6254538ea 100644 --- a/src/apps/testapps/testVec3dInternal.c +++ b/src/apps/testapps/testVec3dInternal.c @@ -40,6 +40,30 @@ SUITE(Vec3dInternal) { "distance to <1,1,2> is 6"); } + TEST(vec3Normalize_smallNonzero) { + // 1e-163 squared underflows to 0, so norm == 0. + // vec3Normalize should produce the zero vector. + Vec3d v = {1e-163, 0, 0}; + + t_assert(v.x != 0.0, "vector is nonzero"); + t_assert(vec3Norm(v) == 0.0, "norm underflows to zero"); + + vec3Normalize(&v); + t_assert(v.x == 0.0 && v.y == 0.0 && v.z == 0.0, + "underflowed vector normalizes to zero"); + } + + TEST(vec3Normalize_dblEpsilonHalf) { + // DBL_EPSILON/2 is small but normalizes fine. + Vec3d v = {DBL_EPSILON / 2.0, 0, 0}; + + t_assert(vec3Norm(v) < DBL_EPSILON, "norm is small but nonzero"); + + vec3Normalize(&v); + t_assert(fabs(v.x - 1.0) < DBL_EPSILON && v.y == 0 && v.z == 0, + "still normalizable to unit vector"); + } + TEST(latLngToVec3) { Vec3d origin = {0}; diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index ec0f09235b..39eec74d6f 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -83,11 +83,19 @@ static inline double vec3Norm(Vec3d v) { return sqrt(vec3NormSq(v)); } static inline void vec3Normalize(Vec3d *v) { double norm = vec3Norm(*v); - if (norm == 0.0) return; - double inv = 1.0 / norm; - v->x *= inv; - v->y *= inv; - v->z *= inv; + + // Norm can be zero either from true zero vector, or from squaring + // underflowing to zero. + // If the norm is nonzero, we normalize v using it. + // If the norm is zero, we set the vector to be exactly zero. + double s = 0.0; + if (norm > 0.0) { + s = 1.0 / norm; + } + + v->x *= s; + v->y *= s; + v->z *= s; } static inline double vec3DistSq(Vec3d v1, Vec3d v2) { From 81ae4686488be173b907bc5bdf96dab62d8ac834 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 9 Apr 2026 00:09:04 -0700 Subject: [PATCH 86/91] static void _hex2dToVec3 --- src/h3lib/lib/faceijk.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 59c71f1848..34e1c41761 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -451,7 +451,8 @@ static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v) { * grid relative to the specified resolution. * @param v3 Output: the 3D coordinates of the cell center point */ -void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, Vec3d *v3) { +static void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, + Vec3d *v3) { // calculate (r, theta) in hex2d double r = _v2dMag(v); From 04275a25d8138fdac0c502a81f082919032da8d7 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 9 Apr 2026 00:12:29 -0700 Subject: [PATCH 87/91] _faceIjkToVec3 g --- src/h3lib/include/faceijk.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/include/faceijk.h b/src/h3lib/include/faceijk.h index 8c20e9ba4d..77ef642bc9 100644 --- a/src/h3lib/include/faceijk.h +++ b/src/h3lib/include/faceijk.h @@ -72,7 +72,7 @@ typedef enum { // Internal functions void _vec3ToFaceIjk(Vec3d p, int res, FaceIJK *h); -void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *v3); +void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *g); void _faceIjkToCellBoundary(const FaceIJK *h, int res, int start, int length, CellBoundary *g); void _faceIjkPentToCellBoundary(const FaceIJK *h, int res, int start, From a9acd9e49c70c3991dc68b9cfcced22e8507dfbf Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 9 Apr 2026 14:45:07 -0700 Subject: [PATCH 88/91] fix doc comment on _faceIjkToVec3 --- src/h3lib/lib/faceijk.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 34e1c41761..66d2428710 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -501,7 +501,7 @@ static void _hex2dToVec3(const Vec2d *v, int face, int res, int substrate, * * @param h The FaceIJK address of the cell. * @param res The H3 resolution of the cell. - * @param v3 Output: The 3D coordinates of the cell center point. + * @param g Output: The 3D coordinates of the cell center point. */ void _faceIjkToVec3(const FaceIJK *h, int res, Vec3d *g) { Vec2d v; From 466a712f453ff841563a17b6b0f6eb1fb7b48554 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 9 Apr 2026 16:05:09 -0700 Subject: [PATCH 89/91] avoid Vec3d casts --- src/h3lib/include/vec3d.h | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/h3lib/include/vec3d.h b/src/h3lib/include/vec3d.h index 39eec74d6f..9bc3e0f012 100644 --- a/src/h3lib/include/vec3d.h +++ b/src/h3lib/include/vec3d.h @@ -43,34 +43,38 @@ typedef struct { /** Convert latitude and longitude to a unit Vec3d on the sphere. */ static inline Vec3d latLngToVec3(LatLng geo) { double r = cos(geo.lat); - return (Vec3d){ - cos(geo.lng) * r, - sin(geo.lng) * r, - sin(geo.lat), + Vec3d out = { + .x = cos(geo.lng) * r, + .y = sin(geo.lng) * r, + .z = sin(geo.lat), }; + return out; } static inline LatLng vec3ToLatLng(Vec3d v) { - return (LatLng){ - asin(v.z), - atan2(v.y, v.x), + LatLng out = { + .lat = asin(v.z), + .lng = atan2(v.y, v.x), }; + return out; } static inline Vec3d vec3LinComb(double a, Vec3d v1, double b, Vec3d v2) { - return (Vec3d){ - a * v1.x + b * v2.x, - a * v1.y + b * v2.y, - a * v1.z + b * v2.z, + Vec3d out = { + .x = a * v1.x + b * v2.x, + .y = a * v1.y + b * v2.y, + .z = a * v1.z + b * v2.z, }; + return out; } static inline Vec3d vec3Cross(Vec3d v1, Vec3d v2) { - return (Vec3d){ - v1.y * v2.z - v1.z * v2.y, - v1.z * v2.x - v1.x * v2.z, - v1.x * v2.y - v1.y * v2.x, + Vec3d out = { + .x = v1.y * v2.z - v1.z * v2.y, + .y = v1.z * v2.x - v1.x * v2.z, + .z = v1.x * v2.y - v1.y * v2.x, }; + return out; } static inline double vec3Dot(Vec3d v1, Vec3d v2) { From 665fd28a6f6d6f472683f562cd2dfe5e56eb6385 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Fri, 10 Apr 2026 09:53:36 -0700 Subject: [PATCH 90/91] unit vector comments --- src/h3lib/lib/faceijk.c | 14 ++++++++++---- src/h3lib/lib/h3Index.c | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/h3lib/lib/faceijk.c b/src/h3lib/lib/faceijk.c index 66d2428710..6fb54de9a3 100644 --- a/src/h3lib/lib/faceijk.c +++ b/src/h3lib/lib/faceijk.c @@ -340,12 +340,14 @@ static const int unitScaleByCIIres[] = { // Forward declares to make diff nicer // TODO: remove and reorder functions after landing static void _vec3ToHex2d(const Vec3d *p, int res, int *face, Vec2d *v); -static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd); +static void _vec3ToClosestFace(const Vec3d *v, int *face, double *sqd); /** * Encodes a Vec3d coordinate to the FaceIJK address of the containing * cell at the specified resolution. * + * Vec3d p is expected to be on the unit sphere. + * * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. * @param h Output: FaceIJK address of the containing cell at resolution res. @@ -398,6 +400,8 @@ static inline double _vec3AzimuthRads(Vec3d p1, Vec3d p2) { * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing 2D hex coordinates relative to that face center. * + * Vec3d p is expected to be on the unit sphere. + * * @param p The Vec3d coordinates to encode. * @param res The desired H3 resolution for the encoding. * @param face Output: The icosahedral face containing the coordinates. @@ -957,17 +961,19 @@ Overage _adjustPentVertOverage(FaceIJK *fijk, int res) { * Encodes a coordinate on the sphere to the corresponding icosahedral face and * containing the squared euclidean distance to that face center. * - * @param v3 The Vec3d coordinates to encode. + * Vec3d v is expected to be on the unit sphere. + * + * @param v The Vec3d coordinates to encode. * @param face Output: The icosahedral face containing the coordinates. * @param sqd Output: The squared euclidean distance to its face center. */ -static void _vec3ToClosestFace(const Vec3d *v3, int *face, double *sqd) { +static void _vec3ToClosestFace(const Vec3d *v, int *face, double *sqd) { *face = 0; // The distance between two farthest points is 2.0, therefore the square of // the distance between two points should always be less or equal than 4.0 . *sqd = 5.0; for (int f = 0; f < NUM_ICOSA_FACES; ++f) { - double sqdT = vec3DistSq(faceCenterPoint[f], *v3); + double sqdT = vec3DistSq(faceCenterPoint[f], *v); if (sqdT < *sqd) { *face = f; *sqd = sqdT; diff --git a/src/h3lib/lib/h3Index.c b/src/h3lib/lib/h3Index.c index 9c66ccbc16..64c11c9ef5 100644 --- a/src/h3lib/lib/h3Index.c +++ b/src/h3lib/lib/h3Index.c @@ -1052,7 +1052,7 @@ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { * Encodes a coordinate on the sphere to the H3 index of the containing cell at * the specified resolution. * - * The cartesian 3D coordinate is expected to be on the unit sphere. + * Vec3d v is expected to be on the unit sphere. * * @param v The 3D cartesian coordinates to encode. * @param res The desired H3 resolution for the encoding. From 5177e31b68f4d9776da29b55e03c7d3ec17d48a9 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sun, 12 Apr 2026 08:41:24 -0700 Subject: [PATCH 91/91] add vec3ToCell tests --- src/apps/testapps/testVec3.c | 75 ++++++++++++++++++++++++++++++------ 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/src/apps/testapps/testVec3.c b/src/apps/testapps/testVec3.c index e89727cb5c..7c78147520 100644 --- a/src/apps/testapps/testVec3.c +++ b/src/apps/testapps/testVec3.c @@ -18,9 +18,9 @@ * @brief Tests the Vec3d helpers used by the geodesic polyfill path. */ +#include #include -#include "constants.h" #include "h3Index.h" #include "test.h" #include "vec3d.h" @@ -36,21 +36,23 @@ SUITE(Vec3d) { Vec3d i = {.x = 1.0, .y = 0.0, .z = 0.0}; Vec3d j = {.x = 0.0, .y = 1.0, .z = 0.0}; Vec3d k = vec3Cross(i, j); - t_assert(fabs(k.x - 0.0) < EPSILON, "x component zero"); - t_assert(fabs(k.y - 0.0) < EPSILON, "y component zero"); - t_assert(fabs(k.z - 1.0) < EPSILON, "z component one"); - t_assert(fabs(vec3Dot(k, i)) < EPSILON, "cross is orthogonal to i"); - t_assert(fabs(vec3Dot(k, j)) < EPSILON, "cross is orthogonal to j"); + t_assert(fabs(k.x - 0.0) < DBL_EPSILON, "x component zero"); + t_assert(fabs(k.y - 0.0) < DBL_EPSILON, "y component zero"); + t_assert(fabs(k.z - 1.0) < DBL_EPSILON, "z component one"); + t_assert(fabs(vec3Dot(k, i)) < DBL_EPSILON, "cross is orthogonal to i"); + t_assert(fabs(vec3Dot(k, j)) < DBL_EPSILON, "cross is orthogonal to j"); } TEST(normalizeAndMagnitude) { Vec3d v = {.x = 3.0, .y = -4.0, .z = 12.0}; double magSq = vec3NormSq(v); - t_assert(fabs(magSq - 169.0) < EPSILON, "magnitude squared matches"); - t_assert(fabs(vec3Norm(v) - 13.0) < EPSILON, "magnitude matches"); + t_assert(fabs(magSq - 169.0) < DBL_EPSILON, + "magnitude squared matches"); + t_assert(fabs(vec3Norm(v) - 13.0) < DBL_EPSILON, "magnitude matches"); vec3Normalize(&v); - t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, "normalized vector is unit"); + t_assert(fabs(vec3Norm(v) - 1.0) < DBL_EPSILON, + "normalized vector is unit"); Vec3d zero = {.x = 0.0, .y = 0.0, .z = 0.0}; vec3Normalize(&zero); @@ -61,14 +63,14 @@ SUITE(Vec3d) { TEST(distance) { Vec3d a = {.x = 0.0, .y = 0.0, .z = 0.0}; Vec3d b = {.x = 1.0, .y = 2.0, .z = 2.0}; - t_assert(fabs(vec3DistSq(a, b) - 9.0) < EPSILON, + t_assert(fabs(vec3DistSq(a, b) - 9.0) < DBL_EPSILON, "distance squared matches"); } TEST(latLngToVec3_unitSphere) { LatLng geo = {.lat = 0.5, .lng = -1.3}; Vec3d v = latLngToVec3(geo); - t_assert(fabs(vec3Norm(v) - 1.0) < 1e-12, + t_assert(fabs(vec3Norm(v) - 1.0) < DBL_EPSILON, "converted vector lives on the unit sphere"); } @@ -81,6 +83,57 @@ SUITE(Vec3d) { "resolution above max is rejected"); } + TEST(cellToVec3_unitSphere) { + // cellToVec3 should return a point on the unit sphere. + LatLng p = {.lat = 0.6, .lng = -1.2}; + H3Index h; + t_assertSuccess(H3_EXPORT(latLngToCell)(&p, 5, &h)); + + Vec3d v; + t_assertSuccess(cellToVec3(h, &v)); + t_assert(fabs(vec3Norm(v) - 1.0) < DBL_EPSILON, + "cellToVec3 result is on the unit sphere"); + } + + TEST(cellToVec3_matchesCellToLatLng) { + // vec3ToLatLng(cellToVec3(cell)) should agree with cellToLatLng. + LatLng p = {.lat = 0.3, .lng = 2.1}; + H3Index h; + t_assertSuccess(H3_EXPORT(latLngToCell)(&p, 7, &h)); + + Vec3d v; + t_assertSuccess(cellToVec3(h, &v)); + LatLng fromVec3 = vec3ToLatLng(v); + + LatLng fromCell; + t_assertSuccess(H3_EXPORT(cellToLatLng)(h, &fromCell)); + + t_assert(fabs(fromVec3.lat - fromCell.lat) < DBL_EPSILON, + "lat matches cellToLatLng"); + t_assert(fabs(fromVec3.lng - fromCell.lng) < DBL_EPSILON, + "lng matches cellToLatLng"); + } + + TEST(cellToVec3_roundTrip) { + // vec3ToCell(cellToVec3(cell)) should return the same cell. + LatLng p = {.lat = -0.4, .lng = 0.8}; + H3Index h; + t_assertSuccess(H3_EXPORT(latLngToCell)(&p, 9, &h)); + + Vec3d v; + t_assertSuccess(cellToVec3(h, &v)); + + H3Index h2; + t_assertSuccess(vec3ToCell(&v, 9, &h2)); + t_assert(h2 == h, "round-trip through Vec3d returns same cell"); + } + + TEST(cellToVec3_invalidCell) { + Vec3d v; + t_assert(cellToVec3(0x7fffffffffffffff, &v) == E_CELL_INVALID, + "invalid cell gives E_CELL_INVALID"); + } + TEST(vec3ToCell_nonFinite) { H3Index out; Vec3d nanX = {.x = NAN, .y = 0.0, .z = 0.0};