public static H3Index GetUniDirectionalEdge(Code.H3Index origin, Code.H3Index destination) { return(new H3Index { Value = H3UniEdge.getH3UnidirectionalEdge(origin, destination).value }); }
public static HexRangeDistancesResult HexRangeDistances(Code.H3Index origin, int k) { int maxSize = Algos.maxKringSize(k); var outHexes = MakeEmpty(maxSize); var distances = new int[maxSize].ToList(); var result = Algos.hexRangeDistances(origin, k, ref outHexes, ref distances); List <HexRangeMeasurement> combiner = new List <HexRangeMeasurement>(); for (int i = 0; i < maxSize; i++) { combiner.Add ( new HexRangeMeasurement { Index = new H3Index { Value = outHexes[i].value }, Distance = distances[i] } ); } return(new HexRangeDistancesResult { Result = result, Values = combiner.ToArray() }); }
public static H3Index GetDestinationH3FromUniDirectionalEdge(Code.H3Index edge) { return(new H3Index { Value = H3UniEdge.getDestinationH3IndexFromUnidirectionalEdge(edge) }); }
public static string H3ToString(Code.H3Index h3) { string s = ""; Code.H3Index.h3ToString(h3, ref s, 17); return(s); }
public static H3Index GetOriginH3FromUniDirectionalEdge(Code.H3Index edge) { return(new H3Index { Value = H3UniEdge.getOriginH3IndexFromUnidirectionalEdge(edge).value }); }
public static H3Index H3ToParent(Code.H3Index h3, int parentRes) { return(new H3Index { Value = Code.H3Index.h3ToParent(h3, parentRes) }); }
public static HexRangeResult HexRanges(Code.H3Index h3Set, int k) { var tempList = new List <Code.H3Index> { h3Set }; return(HexRanges(tempList, k)); }
public static Api.GeoCoord H3ToGeo(Code.H3Index h3) { var blank = new Code.GeoCoord(); Code.H3Index.h3ToGeo(h3, ref blank); return(new GeoCoord { Latitude = blank.lat, Longitude = blank.lon }); }
public static H3Index[] H3ToChildren(Code.H3Index h3, int childRes) { var maxSize = MaxH3ToChildrenSize(h3, childRes); var children = MakeEmpty(maxSize); Code.H3Index.h3ToChildren(h3, childRes, ref children); return(children.Select(v => new H3Index { Value = v.value }).ToArray()); }
public static H3Index[] GetH3IndexesFromUnidirectionalEdge(Code.H3Index edge) { List <Code.H3Index> cells = new List <Code.H3Index> { 0, 0 }; H3UniEdge.getH3IndexesFromUnidirectionalEdge(edge, ref cells); return(cells.Select(v => new H3Index { Value = v.value }).ToArray()); }
public static H3Index[] Kring(Code.H3Index origin, int k) { int maxSize = Algos.maxKringSize(k); var outHexes = MakeEmpty(maxSize); Algos.kRing(origin, k, ref outHexes); return(outHexes.Select(v => new H3Index { Value = v.value }).ToArray()); }
public static H3Index[] GetH3UniDirectionalEdgesFromHexagon(Code.H3Index origin) { var cells = MakeEmpty(6); H3UniEdge.getH3UnidirectionalEdgesFromHexagon(origin, cells); return(cells.Where(v => v != 0) .Select(v => new H3Index { Value = v.value }) .ToArray()); }
public static GeoBoundary GetH3UnidirectionalEdgeBoundary(Code.H3Index edge) { Code.GeoBoundary gb = new Code.GeoBoundary(); H3UniEdge.getH3UnidirectionalEdgeBoundary(edge, ref gb); var newVerts = gb.verts.Select(v => new GeoCoord(v)).ToArray(); return(new GeoBoundary { VertexCount = gb.numVerts, Vertices = newVerts }); }
public static HexRangeResult experimentalLocalIjToH3(Code.H3Index origin, Code.LocalIJ.CoordIJ ij) { Code.H3Index h3 = new Code.H3Index(0); var result = LocalIJ.experimentalLocalIjToH3(origin, ij, ref h3); return(new HexRangeResult { Result = result, Indexes = new H3Index[] { new H3Index { Value = h3.value } } }); }
public static HexRangeResult HexRange(Code.H3Index origin, int k) { var temp = MakeEmpty(Algos.maxKringSize(k)); var result = Algos.hexRange(origin, k, ref temp); return(new HexRangeResult { Result = result, Indexes = temp.Select(t => new Api.H3Index { Value = t.value }).ToArray() }); }
public static ExperimentalIJ ExperimentalH3ToLocalIj(Code.H3Index origin, Code.H3Index h3) { LocalIJ.CoordIJ ij = new LocalIJ.CoordIJ(); int result = LocalIJ.experimentalH3ToLocalIj(origin, h3, ij); return(new ExperimentalIJ { Result = result, IJ = new CoordIJ { I = ij.i, J = ij.j } }); }
/// <summary> /// Returns whether or not an H3 index is valid. /// </summary> /// <param name="h">The H3 index to validate.</param> /// <returns>1 if the H3 index if valid, and 0 if it is not.</returns> /// <!-- Based off 3.1.1 --> public static int h3IsValid(H3Index h) { if (H3_GET_MODE(ref h) != Constants.H3_HEXAGON_MODE) { return(0); } int baseCell = H3_GET_BASE_CELL(h); if (baseCell < 0 || baseCell >= Constants.NUM_BASE_CELLS) { return(0); } int res = H3_GET_RESOLUTION(h); bool foundFirstNonZeroDigit = false; if (res < 0 || res > Constants.MAX_H3_RES) { return(0); } for (int r = 1; r <= res; r++) { Direction digit = H3_GET_INDEX_DIGIT(h, r); if (!foundFirstNonZeroDigit && digit != Direction.CENTER_DIGIT) { foundFirstNonZeroDigit = true; if (BaseCells._isBaseCellPentagon(baseCell) && digit == Direction.K_AXES_DIGIT) { return(0); } } if (digit < Direction.CENTER_DIGIT || digit >= Direction.NUM_DIGITS) { return(0); } } for (int r = res + 1; r <= Constants.MAX_H3_RES; r++) { Direction digit = H3_GET_INDEX_DIGIT(h, r); if (digit != Direction.INVALID_DIGIT) { return(0); } } return(1); }
/// <summary> /// Internal: Create a vertex graph from a set of hexagons. It is the /// responsibility of the caller to call destroyVertexGraph on the populated /// graph, otherwise the memory in the graph nodes will not be freed. /// </summary> /// /// <param name="h3Set">Set of hexagons</param> /// <param name="numHexes">Number of hexagons in the set</param> /// <param name="graph">Output graph</param> /// <!-- Based off 3.1.1 --> public static void h3SetToVertexGraph(ref List <H3Index> h3Set, int numHexes, ref VertexGraph graph) { GeoBoundary vertices = new GeoBoundary(); GeoCoord fromVertex = new GeoCoord(); GeoCoord toVertex = new GeoCoord(); VertexGraph.VertexNode edge; if (numHexes < 1) { // We still need to init the graph, or calls to destroyVertexGraph will // fail graph = new VertexGraph(0, 0); return; } int res = H3Index.H3_GET_RESOLUTION(h3Set[0]); const int minBuckets = 6; // TODO: Better way to calculate/guess? int numBuckets = numHexes > minBuckets ? numHexes : minBuckets; graph = new VertexGraph(numBuckets, res); // Iterate through every hexagon for (int i = 0; i < numHexes; i++) { H3Index.h3ToGeoBoundary(h3Set[i], ref vertices); // iterate through every edge for (int j = 0; j < vertices.numVerts; j++) { fromVertex = new GeoCoord(vertices.verts[j].lat, vertices.verts[j].lon); //fromVtx = vertices.verts[j]; int idx = (j + 1) % vertices.numVerts; toVertex = new GeoCoord(vertices.verts[idx].lat, vertices.verts[idx].lon); //toVtx = vertices.verts[(j + 1) % vertices.numVerts]; // If we've seen this edge already, it will be reversed edge = VertexGraph.findNodeForEdge(ref graph, toVertex, fromVertex); if (edge != null) { // If we've seen it, drop it. No edge is shared by more than 2 // hexagons, so we'll never see it again. VertexGraph.removeVertexNode(ref graph, ref edge); } else { // Add a new node for this edge VertexGraph.addVertexNode(ref graph, fromVertex, toVertex); } } } }
public static Api.GeoBoundary H3ToGeoBoundary(Code.H3Index h3) { var blank = new Code.GeoBoundary(); Code.H3Index.h3ToGeoBoundary(h3, ref blank); var newVerts = blank.verts.Select(v => new Api.GeoCoord(v)).ToArray(); return(new GeoBoundary { VertexCount = blank.numVerts, Vertices = newVerts }); }
/// <summary> /// Initializes an H3 index. /// </summary> /// <param name="hp"> The H3 index to initialize.</param> /// <param name="res"> The H3 resolution to initialize the index to.</param> /// <param name="baseCell"> The H3 base cell to initialize the index to.</param> /// <param name="initDigit"> The H3 digit (0-7) to initialize all of the index digits to.</param> /// <!-- Based off 3.1.1 --> public static void setH3Index(ref H3Index hp, int res, int baseCell, Direction initDigit) { H3Index h = H3_INIT; H3_SET_MODE(ref h, Constants.H3_HEXAGON_MODE); H3_SET_RESOLUTION(ref h, res); H3_SET_BASE_CELL(ref h, baseCell); for (int r = 1; r <= res; r++) { H3_SET_INDEX_DIGIT(ref h, r, (ulong)initDigit); } hp = h; }
/// <summary> /// h3ToChildren takes the given hexagon id and generates all of the children /// at the specified resolution storing them into the provided memory pointer. /// It's assumed that maxH3ToChildrenSize was used to determine the allocation. /// </summary> /// <param name="h" H3Index to find the children of</param> /// <param name="childRes" int the child level to produce</param> /// <param name="children" H3Index* the memory to store the resulting addresses in</param> /// <!-- Based off 3.1.1 --> public static void h3ToChildren(H3Index h, int childRes, ref List <H3Index> children) { children = new List <H3Index>(); int parentRes = H3_GET_RESOLUTION(h); if (parentRes > childRes) { return; } if (parentRes == childRes) { children.Add(h); return; } List <H3Index> current = new List <H3Index> { h }; List <H3Index> realChildren = new List <H3Index>(); int goalRes = childRes; int currentRes = parentRes; while (currentRes < goalRes) { realChildren.Clear(); foreach (var index in current) { int isPentagon = h3IsPentagon(index); for (int m = 0; m < 7; m++) { if (isPentagon > 0 && m == (int)Direction.K_AXES_DIGIT) { realChildren.Add(H3_INVALID_INDEX); } else { var child = makeDirectChild(index, m); realChildren.Add(child); } } } current = new List <H3Index>(realChildren.Where(c => c.value != 0)); currentRes++; } children = new List <H3Index>(current); }
/// <summary> /// k-rings produces indices within k distance of the origin index. /// /// k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and /// all neighboring indices, and so on. /// /// Output is placed in the provided array in no particular order. Elements of /// the output array may be left zero, as can happen when crossing a pentagon. /// </summary> /// <param name="origin">Origin location</param> /// <param name="k">k >= 0</param> /// <param name="out_hex">Zero-filled array which must be of size <see cref="maxKringSize"/>(k)</param> /// <param name="distances">Zero-filled array which must be of size <see cref="maxKringSize"/>(k)</param> /// <!-- Based off 3.1.1 --> public static void kRingDistances(H3Index origin, int k, ref List <H3Index> out_hex, ref List <int> distances) { int maxIdx = maxKringSize(k); // Optimistically try the faster hexRange algorithm first bool failed = hexRangeDistances(origin, k, ref out_hex, ref distances) != 0; if (failed) { // Fast algo failed, fall back to slower, correct algo // and also wipe out array because contents untrustworthy distances.Clear(); distances = new int[out_hex.Count].ToList(); out_hex = new ulong[distances.Count].Select(cell => new H3Index(cell)).ToList(); _kRingInternal(origin, k, ref out_hex, ref distances, maxIdx, 0); } }
/// <summary> /// Determines the center point in spherical coordinates of a cell given by 2D /// hex coordinates on a particular icosahedral face. /// </summary> /// <param name="v">The 2D hex coordinates of the cell.</param> /// <param name="face">The icosahedral face upon which the 2D hex coordinate system is centered</param> /// <param name="res">The H3 resolution of the cell</param> /// <param name="substrate">Indicates whether or not this grid is actually a substrate /// grid relative to the specified resolution.</param> /// <param name="g">The spherical coordinates of the cell center point.</param> /// <!-- Based off 3.1.1 --> public static void _hex2dToGeo(ref Vec2d v, int face, int res, int substrate, ref GeoCoord g) { // calculate (r, theta) in hex2d double r = Vec2d._v2dMag(v); if (r < Constants.EPSILON) { g = faceCenterGeo[face]; return; } double theta = Math.Atan2(v.y, v.x); // scale for current resolution length u for (int i = 0; i < res; i++) { r /= M_SQRT7; } // scale accordingly if this is a substrate grid if (substrate > 0) { r /= 3.0; if (H3Index.isResClassIII(res)) { r /= M_SQRT7; } } r *= Constants.RES0_U_GNOMONIC; // perform inverse gnomonic scaling of r r = Math.Atan(r); // adjust theta for Class III // if a substrate grid, then it's already been adjusted for Class III if (substrate == 0 && H3Index.isResClassIII(res)) { theta = GeoCoord._posAngleRads(theta + Constants.M_AP7_ROT_RADS); } // find theta as an azimuth theta = GeoCoord._posAngleRads(faceAxesAzRadsCII[face, 0] - theta); // now find the point at (r,theta) from the face center GeoCoord._geoAzDistanceRads(ref faceCenterGeo[face], theta, r, ref g); }
/// <summary> /// Internal helper function called recursively for kRingDistances. /// /// Adds the origin index to the output set (treating it as a hash set) /// and recurses to its neighbors, if needed. /// </summary> /// <param name="origin">Origin index</param> /// <param name="k">Maximum distance to move from the origin.</param> /// <param name="outHex">Array treated as a hash set, elements being either H3Index or 0</param> /// <param name="distances">Scratch area, with elements paralleling the out array</param> /// <param name="maxIdx">Size of out and scratch arrays (must be <see cref="maxKringSize"/>(k))</param> /// <param name="curK">Current distance from the origin.</param> /// <remarks>Elements of distances indicate ijk distance from the origin index to the output index</remarks> /// <!-- Based off 3.1.1 --> internal static void _kRingInternal(H3Index origin, int k, ref List <H3Index> outHex, ref List <int> distances, int maxIdx, int curK) { if (origin == 0) { //Debug.WriteLine("Initial origin == 0"); return; } // Put origin in the output array. out is used as a hash set. int off = (int)(origin.value % (ulong)maxIdx); while (outHex[off] != 0 && outHex[off] != origin) { off++; if (off >= maxIdx) { off = 0; } } // We either got a free slot in the hash set or hit a duplicate // We might need to process the duplicate anyways because we got // here on a longer path before. if (outHex[off] == origin && distances[off] <= curK) { return; } outHex[off] = origin; distances[off] = curK; // Base case: reached an index k away from the origin. if (curK >= k) { //Debug.WriteLine("Bailing on curK ({0}) >= k ({1})", curK, k); return; } // Recurse to all neighbors in no particular order. for (int i = 0; i < 6; i++) { int rotations = 0; var hNR = h3NeighborRotations(origin, DIRECTIONS[i], ref rotations); _kRingInternal(hNR, k, ref outHex, ref distances, maxIdx, curK + 1); } }
public static HexRangeResult HexRing(Code.H3Index origin, int k) { int maxSize = (k < 1) ? 1 : k * 6; var outHexes = MakeEmpty(maxSize); var result = Algos.hexRing(origin, k, ref outHexes); return(new HexRangeResult { Result = result, Indexes = outHexes.Select(v => new H3Index { Value = v.value }).ToArray() }); }
/// <summary> /// Returns the destination hexagon from the unidirectional edge H3Index /// </summary> /// <param name="edge">The edge H3 index</param> /// <returns>The destination H3 hexagon index</returns> /// <!-- Based off 3.1.1 --> public static H3Index getDestinationH3IndexFromUnidirectionalEdge(H3Index edge) { if (H3Index.H3_GET_MODE(ref edge) != Constants.H3_UNIEDGE_MODE) { return(H3Index.H3_INVALID_INDEX); } Direction direction = (Direction)H3Index.H3_GET_RESERVED_BITS(edge); int rotations = 0; H3Index destination = Algos .h3NeighborRotations ( getOriginH3IndexFromUnidirectionalEdge(edge), direction, ref rotations ); return(destination); }
/// <summary> /// Produces the grid distance between the two indexes. /// /// This function may fail to find the distance between two indexes, for /// example if they are very far apart. It may also fail when finding /// distances for indexes on opposite sides of a pentagon. /// </summary> /// <param name="origin">Index to find the distance from</param> /// <param name="h3">Index to find the distance to</param> /// <returns> /// The distance, or a negative number if the library could not compute the distance /// </returns> /// <!-- Based off 3.2.0 --> public static int h3Distance(H3Index origin, H3Index h3) { CoordIJK originIjk = new CoordIJK(); CoordIJK h3Ijk = new CoordIJK(); if (h3ToLocalIjk(origin, origin, ref originIjk) != 0) { // Currently there are no tests that would cause getting the coordinates // for an index the same as the origin to fail. return(-1); // LCOV_EXCL_LINE } if (h3ToLocalIjk(origin, h3, ref h3Ijk) != 0) { return(-1); } return(CoordIJK.ijkDistance(originIjk, h3Ijk)); }
/// <summary> /// Produces ij coordinates for an index anchored by an origin. /// /// The coordinate space used by this function may have deleted /// regions or warping due to pentagonal distortion. /// /// Coordinates are only comparable if they come from the same /// origin index. /// /// Failure may occur if the index is too far away from the origin /// or if the index is on the other side of a pentagon. /// /// This function is experimental, and its output is not guaranteed /// to be compatible across different versions of H3. /// </summary> /// <param name="origin">An anchoring index for the ij coordinate system.</param> /// <param name="h3">Index to find the coordinates of</param> /// <param name="out_coord">ij coordinates of the index will be placed here on success</param> /// <returns>0 on success, or another value on failure.</returns> /// <!-- Based off 3.2.0 --> public static int experimentalH3ToLocalIj(H3Index origin, H3Index h3, CoordIJ out_coord) { // This function is currently experimental. Once ready to be part of the // non-experimental API, this function (with the experimental prefix) will // be marked as deprecated and to be removed in the next major version. It // will be replaced with a non-prefixed function name. CoordIJK ijk = new CoordIJK(); int failed = h3ToLocalIjk(origin, h3, ref ijk); if (failed != 0) { return(failed); } CoordIJK.ijkToIj(ijk, ref out_coord); return(0); }
/// <summary> /// Converts a string representation of an H3 index into an H3 index. /// </summary> /// <param name="str"> The string representation of an H3 index.</param> /// <returns> /// The H3 index corresponding to the string argument, or 0 if invalid. /// </returns> /// <!-- Based off 3.1.1 --> public static H3Index stringToH3(string str) { H3Index h = H3_INVALID_INDEX; // A small risk, but for the most part, we're dealing with hex numbers, so let's use that // as our default. if (ulong.TryParse(str, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out ulong ul1)) { return(new H3Index(ul1)); } // If failed, h will be unmodified and we should return 0 anyways. if (ulong.TryParse(str, out ulong ul2)) { return(new H3Index(ul2)); } return(0); }
/// <summary> /// Provides the coordinates defining the unidirectional edge. /// </summary> /// <param name="edge">The unidirectional edge H3Index</param> /// <param name="gb"> /// The geoboundary object to store the edge coordinates. /// </param> /// <!-- Based off 3.1.1 --> public static void getH3UnidirectionalEdgeBoundary(H3Index edge, ref GeoBoundary gb) { // TODO: More efficient solution :) GeoBoundary origin = new GeoBoundary(); GeoBoundary destination = new GeoBoundary(); GeoCoord postponedVertex = new GeoCoord(); bool hasPostponedVertex = false; H3Index.h3ToGeoBoundary(getOriginH3IndexFromUnidirectionalEdge(edge), ref origin); H3Index.h3ToGeoBoundary(getDestinationH3IndexFromUnidirectionalEdge(edge), ref destination); int k = 0; for (int i = 0; i < origin.numVerts; i++) { if (_hasMatchingVertex(origin.verts[i], destination)) { // If we are on vertex 0, we need to handle the case where it's the // end of the edge, not the beginning. if (i == 0 && !_hasMatchingVertex(origin.verts[i + 1], destination)) { postponedVertex = origin.verts[i]; hasPostponedVertex = true; } else { gb.verts[k] = origin.verts[i]; k++; } } } // If we postponed adding the last vertex, add it now if (hasPostponedVertex) { gb.verts[k] = postponedVertex; k++; } gb.numVerts = k; }