/* * Copyright 2018-2019, 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. * 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. */ /** * @module h3 */ import C from '../out/libh3'; import BINDINGS from './bindings'; import { throwIfError, H3LibraryError, JSBindingError, E_RES_DOMAIN, E_UNKNOWN_UNIT, E_ARRAY_LENGTH, E_NULL_INDEX, E_CELL_INVALID } from './errors'; /** * Map of C-defined functions * @type {any} * @private */ const H3 = {}; // Create the bound functions themselves BINDINGS.forEach(function bind(def) { H3[def[0]] = C.cwrap(...def); }); // Alias the hexidecimal base for legibility const BASE_16 = 16; // Alias unused bits for legibility const UNUSED_UPPER_32_BITS = 0; // ---------------------------------------------------------------------------- // Byte size imports const SZ_INT = 4; const SZ_PTR = 4; const SZ_DBL = 8; const SZ_INT64 = 8; const SZ_H3INDEX = H3.sizeOfH3Index(); const SZ_LATLNG = H3.sizeOfLatLng(); const SZ_CELLBOUNDARY = H3.sizeOfCellBoundary(); const SZ_GEOPOLYGON = H3.sizeOfGeoPolygon(); const SZ_GEOLOOP = H3.sizeOfGeoLoop(); const SZ_LINKED_GEOPOLYGON = H3.sizeOfLinkedGeoPolygon(); const SZ_COORDIJ = H3.sizeOfCoordIJ(); // ---------------------------------------------------------------------------- // Custom types /** * 64-bit hexidecimal string representation of an H3 index * @static * @typedef {string} H3Index */ /** * 64-bit hexidecimal string representation of an H3 index, * or two 32-bit integers in little endian order in an array. * @static * @typedef {string | number[]} H3IndexInput */ /** * Coordinates as an `{i, j}` pair * @static * @typedef CoordIJ * @property {number} i * @property {number} j */ /** * Custom JS Error instance with an attached error code. Error codes come from the * core H3 library and can be found [in the H3 docs](https://h3geo.org/docs/library/errors#table-of-error-codes). * @static * @typedef H3Error * @property {string} message * @property {number} code */ /** * Pair of lat,lng coordinates (or lng,lat if GeoJSON output is specified) * @static * @typedef {number[]} CoordPair */ /** * Pair of lower,upper 32-bit ints representing a 64-bit value * @static * @typedef {number[]} SplitLong */ // ---------------------------------------------------------------------------- // Unit constants /** * Length/Area units * @static * @property {string} m * @property {string} m2 * @property {string} km * @property {string} km2 * @property {string} rads * @property {string} rads2 */ export const UNITS = { m: 'm', m2: 'm2', km: 'km', km2: 'km2', rads: 'rads', rads2: 'rads2' }; // ---------------------------------------------------------------------------- // Utilities and helpers /** * Validate a resolution, throwing an error if invalid * @private * @param {unknown} res Value to validate * @return {number} Valid res * @throws {H3Error} If invalid */ function validateRes(res) { if (typeof res !== 'number' || res < 0 || res > 15 || Math.floor(res) !== res) { throw H3LibraryError(E_RES_DOMAIN, res); } return res; } /** * Assert H3 index output, throwing an error if null * @private * @param {H3Index | null} h3Index Index to validate * @return {H3Index} * @throws {H3Error} If invalid */ function validateH3Index(h3Index) { if (!h3Index) throw JSBindingError(E_NULL_INDEX); return h3Index; } const MAX_JS_ARRAY_LENGTH = Math.pow(2, 32) - 1; /** * Validate an array length. JS will throw its own error if you try * to create an array larger than 2^32 - 1, but validating beforehand * allows us to exit early before we try to process large amounts * of data that won't even fit in an output array * @private * @param {number} length Length to validate * @return {number} Valid array length * @throws {H3Error} If invalid */ function validateArrayLength(length) { if (length > MAX_JS_ARRAY_LENGTH) { throw JSBindingError(E_ARRAY_LENGTH, length); } return length; } const INVALID_HEXIDECIMAL_CHAR = /[^0-9a-fA-F]/; /** * Convert an H3 index (64-bit hexidecimal string) into a "split long" - a pair of 32-bit ints * @param {H3IndexInput} h3Index H3 index to check * @return {SplitLong} A two-element array with 32 lower bits and 32 upper bits */ export function h3IndexToSplitLong(h3Index) { if ( Array.isArray(h3Index) && h3Index.length === 2 && Number.isInteger(h3Index[0]) && Number.isInteger(h3Index[1]) ) { return h3Index; } if (typeof h3Index !== 'string' || INVALID_HEXIDECIMAL_CHAR.test(h3Index)) { return [0, 0]; } const upper = parseInt(h3Index.substring(0, h3Index.length - 8), BASE_16); const lower = parseInt(h3Index.substring(h3Index.length - 8), BASE_16); return [lower, upper]; } /** * Convert a 32-bit int to a hexdecimal string * @private * @param {number} num Integer to convert * @return {H3Index} Hexidecimal string */ function hexFrom32Bit(num) { if (num >= 0) { return num.toString(BASE_16); } // Handle negative numbers num = num & 0x7fffffff; let tempStr = zeroPad(8, num.toString(BASE_16)); const topNum = (parseInt(tempStr[0], BASE_16) + 8).toString(BASE_16); tempStr = topNum + tempStr.substring(1); return tempStr; } /** * Get a H3 index string from a split long (pair of 32-bit ints) * @param {number} lower Lower 32 bits * @param {number} upper Upper 32 bits * @return {H3Index} H3 index */ export function splitLongToH3Index(lower, upper) { return hexFrom32Bit(upper) + zeroPad(8, hexFrom32Bit(lower)); } /** * Zero-pad a string to a given length * @private * @param {number} fullLen Target length * @param {string} numStr String to zero-pad * @return {string} Zero-padded string */ function zeroPad(fullLen, numStr) { const numZeroes = fullLen - numStr.length; let outStr = ''; for (let i = 0; i < numZeroes; i++) { outStr += '0'; } outStr = outStr + numStr; return outStr; } // One more than the max size of an unsigned 32-bit int. // Dividing by this number is equivalent to num >>> 32 const UPPER_BIT_DIVISOR = Math.pow(2, 32); /** * Convert a JS double-precision floating point number to a split long * @private * @param {number} num Number to convert * @return {SplitLong} A two-element array with 32 lower bits and 32 upper bits */ function numberToSplitLong(num) { if (typeof num !== 'number') { return [0, 0]; } return [num | 0, (num / UPPER_BIT_DIVISOR) | 0]; } /** * Populate a C-appropriate GeoLoop struct from a polygon array * @private * @param {number[][]} polygonArray Polygon, as an array of coordinate pairs * @param {number} geoLoop C pointer to a GeoLoop struct * @param {boolean} isGeoJson Whether coordinates are in [lng, lat] order per GeoJSON spec * @return {number} C pointer to populated GeoLoop struct */ function polygonArrayToGeoLoop(polygonArray, geoLoop, isGeoJson) { const numVerts = polygonArray.length; const geoCoordArray = C._calloc(numVerts, SZ_LATLNG); // Support [lng, lat] pairs if GeoJSON is specified const latIndex = isGeoJson ? 1 : 0; const lngIndex = isGeoJson ? 0 : 1; for (let i = 0; i < numVerts * 2; i += 2) { C.HEAPF64.set( [polygonArray[i / 2][latIndex], polygonArray[i / 2][lngIndex]].map(degsToRads), geoCoordArray / SZ_DBL + i ); } C.HEAPU32.set([numVerts, geoCoordArray], geoLoop / SZ_INT); return geoLoop; } /** * Create a C-appropriate GeoPolygon struct from an array of polygons * @private * @param {number[][][]} coordinates Array of polygons, each an array of coordinate pairs * @param {boolean} isGeoJson Whether coordinates are in [lng, lat] order per GeoJSON spec * @return {number} C pointer to populated GeoPolygon struct */ function coordinatesToGeoPolygon(coordinates, isGeoJson) { // Any loops beyond the first loop are holes const numHoles = coordinates.length - 1; const geoPolygon = C._calloc(SZ_GEOPOLYGON); // Byte positions within the struct const geoLoopOffset = 0; const numHolesOffset = geoLoopOffset + SZ_GEOLOOP; const holesOffset = numHolesOffset + SZ_INT; // geoLoop is first part of struct polygonArrayToGeoLoop(coordinates[0], geoPolygon + geoLoopOffset, isGeoJson); let holes; if (numHoles > 0) { holes = C._calloc(numHoles, SZ_GEOLOOP); for (let i = 0; i < numHoles; i++) { polygonArrayToGeoLoop(coordinates[i + 1], holes + SZ_GEOLOOP * i, isGeoJson); } } C.setValue(geoPolygon + numHolesOffset, numHoles, 'i32'); C.setValue(geoPolygon + holesOffset, holes, 'i32'); return geoPolygon; } /** * Free memory allocated for a GeoPolygon struct. It is an error to access the struct * after passing it to this method. * @private * @param {number} geoPolygon C pointer to GeoPolygon struct * @return {void} */ function destroyGeoPolygon(geoPolygon) { // Byte positions within the struct const geoLoopOffset = 0; const numHolesOffset = geoLoopOffset + SZ_GEOLOOP; const holesOffset = numHolesOffset + SZ_INT; // Offset of the geoLoop vertex array pointer within the GeoLoop struct const geoLoopArrayOffset = SZ_INT; // Free the outer vertex array C._free(C.getValue(geoPolygon + geoLoopOffset + geoLoopArrayOffset, 'i8*')); // Free the vertex array for the holes, if any const numHoles = C.getValue(geoPolygon + numHolesOffset, 'i32'); if (numHoles > 0) { const holes = C.getValue(geoPolygon + holesOffset, 'i32'); for (let i = 0; i < numHoles; i++) { C._free(C.getValue(holes + SZ_GEOLOOP * i + geoLoopArrayOffset, 'i8*')); } C._free(holes); } C._free(geoPolygon); } /** * Read an H3 index from a pointer to C memory. * @private * @param {number} cAddress Pointer to allocated C memory * @param {number} offset Offset, in number of H3 indexes, in case we're * reading an array * @return {H3Index | null} H3 index, or null if index was invalid */ function readH3IndexFromPointer(cAddress, offset = 0) { const lower = C.getValue(cAddress + SZ_H3INDEX * offset, 'i32'); const upper = C.getValue(cAddress + SZ_H3INDEX * offset + SZ_INT, 'i32'); // The lower bits are allowed to be 0s, but if the upper bits are 0 // this represents an invalid H3 index return upper ? splitLongToH3Index(lower, upper) : null; } /** * Read a boolean (32 bit) from a pointer to C memory. * @private * @param {number} cAddress Pointer to allocated C memory * @param {number} offset Offset, in number of booleans, in case we're * reading an array * @return {Boolean} Boolean value */ function readBooleanFromPointer(cAddress, offset = 0) { const val = C.getValue(cAddress + SZ_INT * offset, 'i32'); return Boolean(val); } /** * Read a double from a pointer to C memory. * @private * @param {number} cAddress Pointer to allocated C memory * @param {number} offset Offset, in number of doubles, in case we're * reading an array * @return {number} Double value */ function readDoubleFromPointer(cAddress, offset = 0) { return C.getValue(cAddress + SZ_DBL * offset, 'double'); } /** * Read a 64-bit int from a pointer to C memory into a JS 64-bit float. * Note that this may lose precision if larger than MAX_SAFE_INTEGER * @private * @param {number} cAddress Pointer to allocated C memory * @return {number} Double value */ function readInt64AsDoubleFromPointer(cAddress) { return H3.readInt64AsDoubleFromPointer(cAddress); } /** * Store an H3 index in C memory. Primarily used as an efficient way to * write sets of hexagons. * @private * @param {H3IndexInput} h3Index H3 index to store * @param {number} cAddress Pointer to allocated C memory * @param {number} offset Offset, in number of H3 indexes from beginning * of the current array */ function storeH3Index(h3Index, cAddress, offset) { // HEAPU32 is a typed array projection on the index space // as unsigned 32-bit integers. This means the index needs // to be divided by SZ_INT (4) to access correctly. Also, // the H3 index is 64 bits, so we skip by twos as we're writing // to 32-bit integers in the proper order. C.HEAPU32.set(h3IndexToSplitLong(h3Index), cAddress / SZ_INT + 2 * offset); } /** * Read an array of 64-bit H3 indexes from C and convert to a JS array of * H3 index strings * @private * @param {number} cAddress Pointer to C ouput array * @param {number} maxCount Max number of hexagons in array. Hexagons with * the value 0 will be skipped, so this isn't * necessarily the length of the output array. * @return {H3Index[]} Array of H3 indexes */ function readArrayOfH3Indexes(cAddress, maxCount) { const out = []; for (let i = 0; i < maxCount; i++) { const h3Index = readH3IndexFromPointer(cAddress, i); if (h3Index !== null) { out.push(h3Index); } } return out; } /** * Store an array of H3 index strings as a C array of 64-bit integers. * @private * @param {number} cAddress Pointer to C input array * @param {H3IndexInput[]} hexagons H3 indexes to pass to the C lib */ function storeArrayOfH3Indexes(cAddress, hexagons) { // Assuming the cAddress points to an already appropriately // allocated space const count = hexagons.length; for (let i = 0; i < count; i++) { storeH3Index(hexagons[i], cAddress, i); } } /** * Populate a C-appropriate LatLng struct from a [lat, lng] array * @private * @param {number} lat Coordinate latitude * @param {number} lng Coordinate longitude * @return {number} C pointer to populated LatLng struct */ function storeLatLng(lat, lng) { const geoCoord = C._calloc(1, SZ_LATLNG); C.HEAPF64.set([lat, lng].map(degsToRads), geoCoord / SZ_DBL); return geoCoord; } /** * Read a single lat or lng value * @private * @param {number} cAddress Pointer to C value * @return {number} */ function readSingleCoord(cAddress) { return radsToDegs(C.getValue(cAddress, 'double')); } /** * Read a LatLng from C and return a [lat, lng] pair. * @private * @param {number} cAddress Pointer to C struct * @return {CoordPair} [lat, lng] pair */ function readLatLng(cAddress) { return [readSingleCoord(cAddress), readSingleCoord(cAddress + SZ_DBL)]; } /** * Read a LatLng from C and return a GeoJSON-style [lng, lat] pair. * @private * @param {number} cAddress Pointer to C struct * @return {CoordPair} [lng, lat] pair */ function readLatLngGeoJson(cAddress) { return [readSingleCoord(cAddress + SZ_DBL), readSingleCoord(cAddress)]; } /** * Read the CellBoundary structure into a list of geo coordinate pairs * @private * @param {number} cellBoundary C pointer to CellBoundary struct * @param {boolean} [geoJsonCoords] Whether to provide GeoJSON coordinate order: [lng, lat] * @param {boolean} [closedLoop] Whether to close the loop * @return {CoordPair[]} Array of geo coordinate pairs */ function readCellBoundary(cellBoundary, geoJsonCoords, closedLoop) { const numVerts = C.getValue(cellBoundary, 'i32'); // Note that though numVerts is an int, the coordinate doubles have to be // aligned to 8 bytes, hence the 8-byte offset here const vertsPos = cellBoundary + SZ_DBL; const out = []; // Support [lng, lat] pairs if GeoJSON is specified const readCoord = geoJsonCoords ? readLatLngGeoJson : readLatLng; for (let i = 0; i < numVerts * 2; i += 2) { out.push(readCoord(vertsPos + SZ_DBL * i)); } if (closedLoop) { // Close loop if GeoJSON is specified out.push(out[0]); } return out; } /** * Read the LinkedGeoPolygon structure into a nested array of MultiPolygon coordinates * @private * @param {number} polygon C pointer to LinkedGeoPolygon struct * @param {boolean} [formatAsGeoJson] Whether to provide GeoJSON output: [lng, lat], closed loops * @return {CoordPair[][][]} MultiPolygon-style output. */ function readMultiPolygon(polygon, formatAsGeoJson) { const output = []; const readCoord = formatAsGeoJson ? readLatLngGeoJson : readLatLng; let loops; let loop; let coords; let coord; // Loop through the linked structure, building the output while (polygon) { output.push((loops = [])); // Follow ->first pointer loop = C.getValue(polygon, 'i8*'); while (loop) { loops.push((coords = [])); // Follow ->first pointer coord = C.getValue(loop, 'i8*'); while (coord) { coords.push(readCoord(coord)); // Follow ->next pointer coord = C.getValue(coord + SZ_DBL * 2, 'i8*'); } if (formatAsGeoJson) { // Close loop if GeoJSON is requested coords.push(coords[0]); } // Follow ->next pointer loop = C.getValue(loop + SZ_PTR * 2, 'i8*'); } // Follow ->next pointer polygon = C.getValue(polygon + SZ_PTR * 2, 'i8*'); } return output; } /** * Read a CoordIJ from C and return an {i, j} pair. * @private * @param {number} cAddress Pointer to C struct * @return {CoordIJ} {i, j} pair */ function readCoordIJ(cAddress) { return { i: C.getValue(cAddress, 'i32'), j: C.getValue(cAddress + SZ_INT, 'i32') }; } /** * Store an {i, j} pair to a C CoordIJ struct. * @private * @param {number} cAddress Pointer to C memory * @param {CoordIJ} ij {i,j} pair to store * @return {void} */ function storeCoordIJ(cAddress, {i, j}) { C.setValue(cAddress, i, 'i32'); C.setValue(cAddress + SZ_INT, j, 'i32'); } /** * Read an array of positive integers array from C. Negative * values are considered invalid and ignored in output. * @private * @param {number} cAddress Pointer to C array * @param {number} count Length of C array * @return {number[]} Javascript integer array */ function readArrayOfPositiveIntegers(cAddress, count) { const out = []; for (let i = 0; i < count; i++) { const int = C.getValue(cAddress + SZ_INT * i, 'i32'); if (int >= 0) { out.push(int); } } return out; } // ---------------------------------------------------------------------------- // Public API functions: Core /** * Whether a given string represents a valid H3 index * @static * @param {H3IndexInput} h3Index H3 index to check * @return {boolean} Whether the index is valid */ export function isValidCell(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); return Boolean(H3.isValidCell(lower, upper)); } /** * Whether the given H3 index is a pentagon * @static * @param {H3IndexInput} h3Index H3 index to check * @return {boolean} isPentagon */ export function isPentagon(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); return Boolean(H3.isPentagon(lower, upper)); } /** * Whether the given H3 index is in a Class III resolution (rotated versus * the icosahedron and subject to shape distortion adding extra points on * icosahedron edges, making them not true hexagons). * @static * @param {H3IndexInput} h3Index H3 index to check * @return {boolean} isResClassIII */ export function isResClassIII(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); return Boolean(H3.isResClassIII(lower, upper)); } /** * Get the number of the base cell for a given H3 index * @static * @param {H3IndexInput} h3Index H3 index to get the base cell for * @return {number} Index of the base cell (0-121) */ export function getBaseCellNumber(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); return H3.getBaseCellNumber(lower, upper); } /** * Get the indices of all icosahedron faces intersected by a given H3 index * @static * @param {H3IndexInput} h3Index H3 index to get faces for * @return {number[]} Indices (0-19) of all intersected faces * @throws {H3Error} If input is invalid */ export function getIcosahedronFaces(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); const countPtr = C._malloc(SZ_INT); try { throwIfError(H3.maxFaceCount(lower, upper, countPtr)); const count = C.getValue(countPtr, 'i32'); const faces = C._malloc(SZ_INT * count); try { throwIfError(H3.getIcosahedronFaces(lower, upper, faces)); return readArrayOfPositiveIntegers(faces, count); } finally { C._free(faces); } } finally { C._free(countPtr); } } /** * Returns the resolution of an H3 index * @static * @param {H3IndexInput} h3Index H3 index to get resolution * @return {number} The number (0-15) resolution, or -1 if invalid */ export function getResolution(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); if (!H3.isValidCell(lower, upper)) { // Compatability with stated API return -1; } return H3.getResolution(lower, upper); } /** * Get the hexagon containing a lat,lon point * @static * @param {number} lat Latitude of point * @param {number} lng Longtitude of point * @param {number} res Resolution of hexagons to return * @return {H3Index} H3 index * @throws {H3Error} If input is invalid */ export function latLngToCell(lat, lng, res) { const latLng = C._malloc(SZ_LATLNG); // Slightly more efficient way to set the memory C.HEAPF64.set([lat, lng].map(degsToRads), latLng / SZ_DBL); // Read value as a split long const h3Index = C._malloc(SZ_H3INDEX); try { throwIfError(H3.latLngToCell(latLng, res, h3Index)); return validateH3Index(readH3IndexFromPointer(h3Index)); } finally { C._free(h3Index); C._free(latLng); } } /** * Get the lat,lon center of a given hexagon * @static * @param {H3IndexInput} h3Index H3 index * @return {CoordPair} Point as a [lat, lng] pair * @throws {H3Error} If input is invalid */ export function cellToLatLng(h3Index) { const latLng = C._malloc(SZ_LATLNG); const [lower, upper] = h3IndexToSplitLong(h3Index); try { throwIfError(H3.cellToLatLng(lower, upper, latLng)); return readLatLng(latLng); } finally { C._free(latLng); } } /** * Get the vertices of a given hexagon (or pentagon), as an array of [lat, lng] * points. For pentagons and hexagons on the edge of an icosahedron face, this * function may return up to 10 vertices. * @static * @param {H3IndexInput} h3Index H3 index * @param {boolean} [formatAsGeoJson] Whether to provide GeoJSON output: [lng, lat], closed loops * @return {CoordPair[]} Array of [lat, lng] pairs * @throws {H3Error} If input is invalid */ export function cellToBoundary(h3Index, formatAsGeoJson) { const cellBoundary = C._malloc(SZ_CELLBOUNDARY); const [lower, upper] = h3IndexToSplitLong(h3Index); try { throwIfError(H3.cellToBoundary(lower, upper, cellBoundary)); return readCellBoundary(cellBoundary, formatAsGeoJson, formatAsGeoJson); } finally { C._free(cellBoundary); } } // ---------------------------------------------------------------------------- // Public API functions: Algorithms /** * Get the parent of the given hexagon at a particular resolution * @static * @param {H3IndexInput} h3Index H3 index to get parent for * @param {number} res Resolution of hexagon to return * @return {H3Index} H3 index of parent, or null for invalid input * @throws {H3Error} If input is invalid */ export function cellToParent(h3Index, res) { const [lower, upper] = h3IndexToSplitLong(h3Index); const parent = C._malloc(SZ_H3INDEX); try { throwIfError(H3.cellToParent(lower, upper, res, parent)); return validateH3Index(readH3IndexFromPointer(parent)); } finally { C._free(parent); } } /** * Get the children/descendents of the given hexagon at a particular resolution * @static * @param {H3IndexInput} h3Index H3 index to get children for * @param {number} res Resolution of hexagons to return * @return {H3Index[]} H3 indexes of children, or empty array for invalid input * @throws {H3Error} If resolution is invalid or output is too large for JS */ export function cellToChildren(h3Index, res) { // Bad input in this case can potentially result in high computation volume // using the current C algorithm. Validate and return an empty array on failure. if (!isValidCell(h3Index)) { return []; } const [lower, upper] = h3IndexToSplitLong(h3Index); const count = validateArrayLength(cellToChildrenSize(h3Index, res)); const hexagons = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.cellToChildren(lower, upper, res, hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } /** * Get the number of children for a cell at a given resolution * @static * @param {H3IndexInput} h3Index H3 index to get child count for * @param {number} res Child resolution * @return {number} Number of children at res for the given cell * @throws {H3Error} If cell or parentRes are invalid */ export function cellToChildrenSize(h3Index, res) { if (!isValidCell(h3Index)) { throw H3LibraryError(E_CELL_INVALID); } const [lower, upper] = h3IndexToSplitLong(h3Index); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.cellToChildrenSize(lower, upper, res, countPtr)); return readInt64AsDoubleFromPointer(countPtr); } finally { C._free(countPtr); } } /** * Get the center child of the given hexagon at a particular resolution * @static * @param {H3IndexInput} h3Index H3 index to get center child for * @param {number} res Resolution of cell to return * @return {H3Index} H3 index of child, or null for invalid input * @throws {H3Error} If resolution is invalid */ export function cellToCenterChild(h3Index, res) { const [lower, upper] = h3IndexToSplitLong(h3Index); const centerChild = C._malloc(SZ_H3INDEX); try { throwIfError(H3.cellToCenterChild(lower, upper, res, centerChild)); return validateH3Index(readH3IndexFromPointer(centerChild)); } finally { C._free(centerChild); } } /** * Get the position of the cell within an ordered list of all children of the * cell's parent at the specified resolution. * @static * @param {H3IndexInput} h3Index H3 index to get child pos for * @param {number} parentRes Resolution of reference parent * @return {number} Position of child within parent at parentRes * @throws {H3Error} If cell or parentRes are invalid */ export function cellToChildPos(h3Index, parentRes) { const [lower, upper] = h3IndexToSplitLong(h3Index); const childPos = C._malloc(SZ_INT64); try { throwIfError(H3.cellToChildPos(lower, upper, parentRes, childPos)); return readInt64AsDoubleFromPointer(childPos); } finally { C._free(childPos); } } /** * Get the child cell at a given position within an ordered list of all children * at the specified resolution * @static * @param {number} childPos Position of the child cell to get * @param {H3IndexInput} h3Index H3 index of the parent cell * @param {number} childRes Resolution of child cell to return * @return {H3Index} H3 index of child * @throws {H3Error} If input is invalid */ export function childPosToCell(childPos, h3Index, childRes) { const [cpLower, cpUpper] = numberToSplitLong(childPos); const [lower, upper] = h3IndexToSplitLong(h3Index); const child = C._malloc(SZ_H3INDEX); try { throwIfError(H3.childPosToCell(cpLower, cpUpper, lower, upper, childRes, child)); return validateH3Index(readH3IndexFromPointer(child)); } finally { C._free(child); } } /** * Get all hexagons in a k-ring around a given center. The order of the hexagons is undefined. * @static * @param {H3IndexInput} h3Index H3 index of center hexagon * @param {number} ringSize Radius of k-ring * @return {H3Index[]} H3 indexes for all hexagons in ring * @throws {H3Error} If input is invalid or output is too large for JS */ export function gridDisk(h3Index, ringSize) { const [lower, upper] = h3IndexToSplitLong(h3Index); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.maxGridDiskSize(ringSize, countPtr)); const count = validateArrayLength(readInt64AsDoubleFromPointer(countPtr)); const hexagons = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.gridDisk(lower, upper, ringSize, hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } finally { C._free(countPtr); } } /** * Get all hexagons in a k-ring around a given center, in an array of arrays * ordered by distance from the origin. The order of the hexagons within each ring is undefined. * @static * @param {H3IndexInput} h3Index H3 index of center hexagon * @param {number} ringSize Radius of k-ring * @return {H3Index[][]} Array of arrays with H3 indexes for all hexagons each ring * @throws {H3Error} If input is invalid or output is too large for JS */ export function gridDiskDistances(h3Index, ringSize) { const [lower, upper] = h3IndexToSplitLong(h3Index); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.maxGridDiskSize(ringSize, countPtr)); const count = validateArrayLength(readInt64AsDoubleFromPointer(countPtr)); const kRings = C._calloc(count, SZ_H3INDEX); const distances = C._calloc(count, SZ_INT); try { throwIfError(H3.gridDiskDistances(lower, upper, ringSize, kRings, distances)); /** * An array of empty arrays to hold the output * @type {string[][]} * @private */ const out = []; for (let i = 0; i < ringSize + 1; i++) { out.push([]); } // Read the array of hexagons, putting them into the appropriate rings for (let i = 0; i < count; i++) { const cell = readH3IndexFromPointer(kRings, i); const index = C.getValue(distances + SZ_INT * i, 'i32'); // eslint-disable-next-line max-depth if (cell !== null) { out[index].push(cell); } } return out; } finally { C._free(kRings); C._free(distances); } } finally { C._free(countPtr); } } /** * Get all hexagons in a hollow hexagonal ring centered at origin with sides of a given length. * Unlike kRing, this function will throw an error if there is a pentagon anywhere in the ring. * @static * @param {H3IndexInput} h3Index H3 index of center hexagon * @param {number} ringSize Radius of ring * @return {H3Index[]} H3 indexes for all hexagons in ring * @throws {Error} If the algorithm could not calculate the ring * @throws {H3Error} If input is invalid */ export function gridRingUnsafe(h3Index, ringSize) { const maxCount = ringSize === 0 ? 1 : 6 * ringSize; const hexagons = C._calloc(maxCount, SZ_H3INDEX); try { throwIfError(H3.gridRingUnsafe(...h3IndexToSplitLong(h3Index), ringSize, hexagons)); return readArrayOfH3Indexes(hexagons, maxCount); } finally { C._free(hexagons); } } /** * Get all hexagons with centers contained in a given polygon. The polygon * is specified with GeoJson semantics as an array of loops. Each loop is * an array of [lat, lng] pairs (or [lng, lat] if isGeoJson is specified). * The first loop is the perimeter of the polygon, and subsequent loops are * expected to be holes. * @static * @param {number[][] | number[][][]} coordinates * Array of loops, or a single loop * @param {number} res Resolution of hexagons to return * @param {boolean} [isGeoJson] Whether to expect GeoJson-style [lng, lat] * pairs instead of [lat, lng] * @return {H3Index[]} H3 indexes for all hexagons in polygon * @throws {H3Error} If input is invalid or output is too large for JS */ export function polygonToCells(coordinates, res, isGeoJson) { validateRes(res); isGeoJson = Boolean(isGeoJson); // Guard against empty input if (coordinates.length === 0 || coordinates[0].length === 0) { return []; } // Wrap to expected format if a single loop is provided const polygon = typeof coordinates[0][0] === 'number' ? [coordinates] : coordinates; const geoPolygon = coordinatesToGeoPolygon( // @ts-expect-error - There's no way to convince TS that polygon is now number[][][] polygon, isGeoJson ); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.maxPolygonToCellsSize(geoPolygon, res, 0, countPtr)); const count = validateArrayLength(readInt64AsDoubleFromPointer(countPtr)); const hexagons = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.polygonToCells(geoPolygon, res, 0, hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } finally { C._free(countPtr); destroyGeoPolygon(geoPolygon); } } /** * Get the outlines of a set of H3 hexagons, returned in GeoJSON MultiPolygon * format (an array of polygons, each with an array of loops, each an array of * coordinates). Coordinates are returned as [lat, lng] pairs unless GeoJSON * is requested. * * It is the responsibility of the caller to ensure that all hexagons in the * set have the same resolution and that the set contains no duplicates. Behavior * is undefined if duplicates or multiple resolutions are present, and the * algorithm may produce unexpected or invalid polygons. * * @static * @param {H3IndexInput[]} h3Indexes H3 indexes to get outlines for * @param {boolean} [formatAsGeoJson] Whether to provide GeoJSON output: [lng, lat], closed loops * @return {CoordPair[][][]} MultiPolygon-style output. * @throws {H3Error} If input is invalid */ export function cellsToMultiPolygon(h3Indexes, formatAsGeoJson) { // Early exit on empty input if (!h3Indexes || !h3Indexes.length) { return []; } // Set up input set const indexCount = h3Indexes.length; const set = C._calloc(indexCount, SZ_H3INDEX); storeArrayOfH3Indexes(set, h3Indexes); // Allocate memory for output linked polygon const polygon = C._calloc(SZ_LINKED_GEOPOLYGON); try { throwIfError(H3.cellsToLinkedMultiPolygon(set, indexCount, polygon)); return readMultiPolygon(polygon, formatAsGeoJson); } finally { // Clean up H3.destroyLinkedMultiPolygon(polygon); C._free(polygon); C._free(set); } } /** * Compact a set of hexagons of the same resolution into a set of hexagons across * multiple levels that represents the same area. * @static * @param {H3IndexInput[]} h3Set H3 indexes to compact * @return {H3Index[]} Compacted H3 indexes * @throws {H3Error} If the input is invalid (e.g. duplicate hexagons) */ export function compactCells(h3Set) { if (!h3Set || !h3Set.length) { return []; } // Set up input set const count = h3Set.length; const set = C._calloc(count, SZ_H3INDEX); storeArrayOfH3Indexes(set, h3Set); // Allocate memory for compacted hexagons, worst-case is no compaction const compactedSet = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.compactCells(set, compactedSet, count, UNUSED_UPPER_32_BITS)); return readArrayOfH3Indexes(compactedSet, count); } finally { C._free(set); C._free(compactedSet); } } /** * Uncompact a compacted set of hexagons to hexagons of the same resolution * @static * @param {H3IndexInput[]} compactedSet H3 indexes to uncompact * @param {number} res The resolution to uncompact to * @return {H3Index[]} The uncompacted H3 indexes * @throws {H3Error} If the input is invalid (e.g. invalid resolution) */ export function uncompactCells(compactedSet, res) { validateRes(res); if (!compactedSet || !compactedSet.length) { return []; } // Set up input set const count = compactedSet.length; const set = C._calloc(count, SZ_H3INDEX); storeArrayOfH3Indexes(set, compactedSet); // Estimate how many hexagons we need (always overestimates if in error) const uncompactCellSizePtr = C._malloc(SZ_INT64); try { throwIfError( H3.uncompactCellsSize(set, count, UNUSED_UPPER_32_BITS, res, uncompactCellSizePtr) ); const uncompactCellSize = validateArrayLength( readInt64AsDoubleFromPointer(uncompactCellSizePtr) ); // Allocate memory for uncompacted hexagons const uncompactedSet = C._calloc(uncompactCellSize, SZ_H3INDEX); try { throwIfError( H3.uncompactCells( set, count, UNUSED_UPPER_32_BITS, uncompactedSet, uncompactCellSize, UNUSED_UPPER_32_BITS, res ) ); return readArrayOfH3Indexes(uncompactedSet, uncompactCellSize); } finally { C._free(set); C._free(uncompactedSet); } } finally { C._free(uncompactCellSizePtr); } } // ---------------------------------------------------------------------------- // Public API functions: Directed edges /** * Whether two H3 indexes are neighbors (share an edge) * @static * @param {H3IndexInput} origin Origin hexagon index * @param {H3IndexInput} destination Destination hexagon index * @return {boolean} Whether the hexagons share an edge * @throws {H3Error} If the input is invalid */ export function areNeighborCells(origin, destination) { const [oLower, oUpper] = h3IndexToSplitLong(origin); const [dLower, dUpper] = h3IndexToSplitLong(destination); const out = C._malloc(SZ_INT); try { throwIfError(H3.areNeighborCells(oLower, oUpper, dLower, dUpper, out)); return readBooleanFromPointer(out); } finally { C._free(out); } } /** * Get an H3 index representing a unidirectional edge for a given origin and destination * @static * @param {H3IndexInput} origin Origin hexagon index * @param {H3IndexInput} destination Destination hexagon index * @return {H3Index} H3 index of the edge, or null if no edge is shared * @throws {H3Error} If the input is invalid */ export function cellsToDirectedEdge(origin, destination) { const [oLower, oUpper] = h3IndexToSplitLong(origin); const [dLower, dUpper] = h3IndexToSplitLong(destination); const h3Index = C._malloc(SZ_H3INDEX); try { throwIfError(H3.cellsToDirectedEdge(oLower, oUpper, dLower, dUpper, h3Index)); return validateH3Index(readH3IndexFromPointer(h3Index)); } finally { C._free(h3Index); } } /** * Get the origin hexagon from an H3 index representing a unidirectional edge * @static * @param {H3IndexInput} edgeIndex H3 index of the edge * @return {H3Index} H3 index of the edge origin * @throws {H3Error} If the input is invalid */ export function getDirectedEdgeOrigin(edgeIndex) { const [lower, upper] = h3IndexToSplitLong(edgeIndex); const h3Index = C._malloc(SZ_H3INDEX); try { throwIfError(H3.getDirectedEdgeOrigin(lower, upper, h3Index)); return validateH3Index(readH3IndexFromPointer(h3Index)); } finally { C._free(h3Index); } } /** * Get the destination hexagon from an H3 index representing a unidirectional edge * @static * @param {H3IndexInput} edgeIndex H3 index of the edge * @return {H3Index} H3 index of the edge destination * @throws {H3Error} If the input is invalid */ export function getDirectedEdgeDestination(edgeIndex) { const [lower, upper] = h3IndexToSplitLong(edgeIndex); const h3Index = C._malloc(SZ_H3INDEX); try { throwIfError(H3.getDirectedEdgeDestination(lower, upper, h3Index)); return validateH3Index(readH3IndexFromPointer(h3Index)); } finally { C._free(h3Index); } } /** * Whether the input is a valid unidirectional edge * @static * @param {H3IndexInput} edgeIndex H3 index of the edge * @return {boolean} Whether the index is valid */ export function isValidDirectedEdge(edgeIndex) { const [lower, upper] = h3IndexToSplitLong(edgeIndex); return Boolean(H3.isValidDirectedEdge(lower, upper)); } /** * Get the [origin, destination] pair represented by a unidirectional edge * @static * @param {H3IndexInput} edgeIndex H3 index of the edge * @return {H3Index[]} [origin, destination] pair as H3 indexes * @throws {H3Error} If the input is invalid */ export function directedEdgeToCells(edgeIndex) { const [lower, upper] = h3IndexToSplitLong(edgeIndex); const count = 2; const hexagons = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.directedEdgeToCells(lower, upper, hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } /** * Get all of the unidirectional edges with the given H3 index as the origin (i.e. an edge to * every neighbor) * @static * @param {H3IndexInput} h3Index H3 index of the origin hexagon * @return {H3Index[]} List of unidirectional edges * @throws {H3Error} If the input is invalid */ export function originToDirectedEdges(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); const count = 6; const edges = C._calloc(count, SZ_H3INDEX); try { throwIfError(H3.originToDirectedEdges(lower, upper, edges)); return readArrayOfH3Indexes(edges, count); } finally { C._free(edges); } } /** * Get the vertices of a given edge as an array of [lat, lng] points. Note that for edges that * cross the edge of an icosahedron face, this may return 3 coordinates. * @static * @param {H3IndexInput} edgeIndex H3 index of the edge * @param {boolean} [formatAsGeoJson] Whether to provide GeoJSON output: [lng, lat] * @return {CoordPair[]} Array of geo coordinate pairs * @throws {H3Error} If the input is invalid */ export function directedEdgeToBoundary(edgeIndex, formatAsGeoJson) { const cellBoundary = C._malloc(SZ_CELLBOUNDARY); const [lower, upper] = h3IndexToSplitLong(edgeIndex); try { throwIfError(H3.directedEdgeToBoundary(lower, upper, cellBoundary)); return readCellBoundary(cellBoundary, formatAsGeoJson); } finally { C._free(cellBoundary); } } /** * Get the grid distance between two hex indexes. This function may fail * to find the distance between two indexes if they are very far apart or * on opposite sides of a pentagon. * @static * @param {H3IndexInput} origin Origin hexagon index * @param {H3IndexInput} destination Destination hexagon index * @return {number} Distance between hexagons * @throws {H3Error} If input is invalid or the distance could not be calculated */ export function gridDistance(origin, destination) { const [oLower, oUpper] = h3IndexToSplitLong(origin); const [dLower, dUpper] = h3IndexToSplitLong(destination); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.gridDistance(oLower, oUpper, dLower, dUpper, countPtr)); return readInt64AsDoubleFromPointer(countPtr); } finally { C._free(countPtr); } } /** * Given two H3 indexes, return the line of indexes between them (inclusive). * * This function may fail to find the line between two indexes, for * example if they are very far apart. It may also fail when finding * distances for indexes on opposite sides of a pentagon. * * Notes: * * - The specific output of this function should not be considered stable * across library versions. The only guarantees the library provides are * that the line length will be `h3Distance(start, end) + 1` and that * every index in the line will be a neighbor of the preceding index. * - Lines are drawn in grid space, and may not correspond exactly to either * Cartesian lines or great arcs. * * @static * @param {H3IndexInput} origin Origin hexagon index * @param {H3IndexInput} destination Destination hexagon index * @return {H3Index[]} H3 indexes connecting origin and destination * @throws {H3Error} If input is invalid or the line cannot be calculated */ export function gridPathCells(origin, destination) { const [oLower, oUpper] = h3IndexToSplitLong(origin); const [dLower, dUpper] = h3IndexToSplitLong(destination); const countPtr = C._malloc(SZ_INT64); try { throwIfError(H3.gridPathCellsSize(oLower, oUpper, dLower, dUpper, countPtr)); const count = validateArrayLength(readInt64AsDoubleFromPointer(countPtr)); const hexagons = C._calloc(count, SZ_H3INDEX); try { H3.gridPathCells(oLower, oUpper, dLower, dUpper, hexagons); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } finally { C._free(countPtr); } } const LOCAL_IJ_DEFAULT_MODE = 0; /** * Produces IJ coordinates for an H3 index anchored by an origin. * * - The coordinate space used by this function may have deleted * regions or warping due to pentagonal distortion. * - Coordinates are only comparable if they come from the same * origin index. * - Failure may occur if the index is too far away from the origin * or if the index is on the other side of a pentagon. * - This function is experimental, and its output is not guaranteed * to be compatible across different versions of H3. * @static * @param {H3IndexInput} origin Origin H3 index * @param {H3IndexInput} destination H3 index for which to find relative coordinates * @return {CoordIJ} Coordinates as an `{i, j}` pair * @throws {H3Error} If the IJ coordinates cannot be calculated */ export function cellToLocalIj(origin, destination) { const ij = C._malloc(SZ_COORDIJ); try { throwIfError( H3.cellToLocalIj( ...h3IndexToSplitLong(origin), ...h3IndexToSplitLong(destination), LOCAL_IJ_DEFAULT_MODE, ij ) ); return readCoordIJ(ij); } finally { C._free(ij); } } /** * Produces an H3 index for IJ coordinates anchored by an origin. * * - The coordinate space used by this function may have deleted * regions or warping due to pentagonal distortion. * - Coordinates are only comparable if they come from the same * origin index. * - Failure may occur if the index is too far away from the origin * or if the index is on the other side of a pentagon. * - This function is experimental, and its output is not guaranteed * to be compatible across different versions of H3. * @static * @param {H3IndexInput} origin Origin H3 index * @param {CoordIJ} coords Coordinates as an `{i, j}` pair * @return {H3Index} H3 index at the relative coordinates * @throws {H3Error} If the H3 index cannot be calculated */ export function localIjToCell(origin, coords) { // Validate input coords if (!coords || typeof coords.i !== 'number' || typeof coords.j !== 'number') { throw new Error('Coordinates must be provided as an {i, j} object'); } // Allocate memory for the CoordIJ struct and an H3 index to hold the return value const ij = C._malloc(SZ_COORDIJ); const out = C._malloc(SZ_H3INDEX); storeCoordIJ(ij, coords); try { throwIfError( H3.localIjToCell(...h3IndexToSplitLong(origin), ij, LOCAL_IJ_DEFAULT_MODE, out) ); return validateH3Index(readH3IndexFromPointer(out)); } finally { C._free(ij); C._free(out); } } // ---------------------------------------------------------------------------- // Public API functions: Distance/area utilities /** * Great circle distance between two geo points. This is not specific to H3, * but is implemented in the library and provided here as a convenience. * @static * @param {number[]} latLng1 Origin coordinate as [lat, lng] * @param {number[]} latLng2 Destination coordinate as [lat, lng] * @param {string} unit Distance unit (either UNITS.m, UNITS.km, or UNITS.rads) * @return {number} Great circle distance * @throws {H3Error} If the unit is invalid */ export function greatCircleDistance(latLng1, latLng2, unit) { const coord1 = storeLatLng(latLng1[0], latLng1[1]); const coord2 = storeLatLng(latLng2[0], latLng2[1]); let result; switch (unit) { case UNITS.m: result = H3.greatCircleDistanceM(coord1, coord2); break; case UNITS.km: result = H3.greatCircleDistanceKm(coord1, coord2); break; case UNITS.rads: result = H3.greatCircleDistanceRads(coord1, coord2); break; default: result = null; } C._free(coord1); C._free(coord2); if (result === null) { throw JSBindingError(E_UNKNOWN_UNIT, unit); } return result; } /** * Exact area of a given cell * @static * @param {H3IndexInput} h3Index H3 index of the hexagon to measure * @param {string} unit Distance unit (either UNITS.m2, UNITS.km2, or UNITS.rads2) * @return {number} Cell area * @throws {H3Error} If the input is invalid */ export function cellArea(h3Index, unit) { const [lower, upper] = h3IndexToSplitLong(h3Index); const out = C._malloc(SZ_DBL); try { switch (unit) { case UNITS.m2: throwIfError(H3.cellAreaM2(lower, upper, out)); break; case UNITS.km2: throwIfError(H3.cellAreaKm2(lower, upper, out)); break; case UNITS.rads2: throwIfError(H3.cellAreaRads2(lower, upper, out)); break; default: throw JSBindingError(E_UNKNOWN_UNIT, unit); } return readDoubleFromPointer(out); } finally { C._free(out); } } /** * Calculate length of a given unidirectional edge * @static * @param {H3IndexInput} edge H3 index of the edge to measure * @param {string} unit Distance unit (either UNITS.m, UNITS.km, or UNITS.rads) * @return {number} Cell area * @throws {H3Error} If the input is invalid */ export function edgeLength(edge, unit) { const [lower, upper] = h3IndexToSplitLong(edge); const out = C._malloc(SZ_DBL); try { switch (unit) { case UNITS.m: throwIfError(H3.edgeLengthM(lower, upper, out)); break; case UNITS.km: throwIfError(H3.edgeLengthKm(lower, upper, out)); break; case UNITS.rads: throwIfError(H3.edgeLengthRads(lower, upper, out)); break; default: throw JSBindingError(E_UNKNOWN_UNIT, unit); } return readDoubleFromPointer(out); } finally { C._free(out); } } /** * Average hexagon area at a given resolution * @static * @param {number} res Hexagon resolution * @param {string} unit Area unit (either UNITS.m2, UNITS.km2, or UNITS.rads2) * @return {number} Average area * @throws {H3Error} If the input is invalid */ export function getHexagonAreaAvg(res, unit) { validateRes(res); const out = C._malloc(SZ_DBL); try { switch (unit) { case UNITS.m2: throwIfError(H3.getHexagonAreaAvgM2(res, out)); break; case UNITS.km2: throwIfError(H3.getHexagonAreaAvgKm2(res, out)); break; default: throw JSBindingError(E_UNKNOWN_UNIT, unit); } return readDoubleFromPointer(out); } finally { C._free(out); } } /** * Average hexagon edge length at a given resolution * @static * @param {number} res Hexagon resolution * @param {string} unit Distance unit (either UNITS.m, UNITS.km, or UNITS.rads) * @return {number} Average edge length * @throws {H3Error} If the input is invalid */ export function getHexagonEdgeLengthAvg(res, unit) { validateRes(res); const out = C._malloc(SZ_DBL); try { switch (unit) { case UNITS.m: throwIfError(H3.getHexagonEdgeLengthAvgM(res, out)); break; case UNITS.km: throwIfError(H3.getHexagonEdgeLengthAvgKm(res, out)); break; default: throw JSBindingError(E_UNKNOWN_UNIT, unit); } return readDoubleFromPointer(out); } finally { C._free(out); } } // ---------------------------------------------------------------------------- // Public API functions: Vertex mode /** * Find the index for a vertex of a cell. * @static * @param {H3IndexInput} h3Index Cell to find the vertex for * @param {number} vertexNum Number (index) of the vertex to calculate * @return {H3Index} Vertex index * @throws {H3Error} If the input is invalid */ export function cellToVertex(h3Index, vertexNum) { const [lower, upper] = h3IndexToSplitLong(h3Index); const vertexIndex = C._malloc(SZ_H3INDEX); try { throwIfError(H3.cellToVertex(lower, upper, vertexNum, vertexIndex)); return validateH3Index(readH3IndexFromPointer(vertexIndex)); } finally { C._free(vertexIndex); } } /** * Find the indexes for all vertexes of a cell. * @static * @param {H3IndexInput} h3Index Cell to find all vertexes for * @return {H3Index[]} All vertex indexes of this cell * @throws {H3Error} If the input is invalid */ export function cellToVertexes(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); const maxNumVertexes = 6; const vertexIndexes = C._calloc(maxNumVertexes, SZ_H3INDEX); try { throwIfError(H3.cellToVertexes(lower, upper, vertexIndexes)); return readArrayOfH3Indexes(vertexIndexes, maxNumVertexes); } finally { C._free(vertexIndexes); } } /** * Get the lat, lng of a given vertex * @static * @param {H3IndexInput} h3Index A vertex index * @returns {CoordPair} Latitude, longitude coordinates of the vertex * @throws {H3Error} If the input is invalid */ export function vertexToLatLng(h3Index) { const latlng = C._malloc(SZ_LATLNG); const [lower, upper] = h3IndexToSplitLong(h3Index); try { throwIfError(H3.vertexToLatLng(lower, upper, latlng)); return readLatLng(latlng); } finally { C._free(latlng); } } /** * Returns true if the input is a valid vertex index. * @static * @param {H3IndexInput} h3Index An index to test for being a vertex index * @returns {boolean} True if the index represents a vertex */ export function isValidVertex(h3Index) { const [lower, upper] = h3IndexToSplitLong(h3Index); return Boolean(H3.isValidVertex(lower, upper)); } // ---------------------------------------------------------------------------- // Public informational utilities /** * The total count of hexagons in the world at a given resolution. Note that above * resolution 8 the exact count cannot be represented in a JavaScript 32-bit number, * so consumers should use caution when applying further operations to the output. * @static * @param {number} res Hexagon resolution * @return {number} Count * @throws {H3Error} If the resolution is invalid */ export function getNumCells(res) { validateRes(res); const countPtr = C._malloc(SZ_INT64); try { // Get number as a long value throwIfError(H3.getNumCells(res, countPtr)); return readInt64AsDoubleFromPointer(countPtr); } finally { C._free(countPtr); } } /** * Get all H3 indexes at resolution 0. As every index at every resolution > 0 is * the descendant of a res 0 index, this can be used with h3ToChildren to iterate * over H3 indexes at any resolution. * @static * @return {H3Index[]} All H3 indexes at res 0 */ export function getRes0Cells() { const count = H3.res0CellCount(); const hexagons = C._malloc(SZ_H3INDEX * count); try { throwIfError(H3.getRes0Cells(hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } /** * Get the twelve pentagon indexes at a given resolution. * @static * @param {number} res Hexagon resolution * @return {H3Index[]} All H3 pentagon indexes at res * @throws {H3Error} If the resolution is invalid */ export function getPentagons(res) { validateRes(res); const count = H3.pentagonCount(); const hexagons = C._malloc(SZ_H3INDEX * count); try { throwIfError(H3.getPentagons(res, hexagons)); return readArrayOfH3Indexes(hexagons, count); } finally { C._free(hexagons); } } /** * Convert degrees to radians * @static * @param {number} deg Value in degrees * @return {number} Value in radians */ export function degsToRads(deg) { return (deg * Math.PI) / 180; } /** * Convert radians to degrees * @static * @param {number} rad Value in radians * @return {number} Value in degrees */ export function radsToDegs(rad) { return (rad * 180) / Math.PI; }