/// <summary> /// Provides all of the unidirectional edges from the current H3Index. /// </summary> /// <param name="origin">The origin hexagon H3Index to find edges for.</param> /// <param name="edges">The memory to store all of the edges inside.</param> /// <!-- Based off 3.1.1 --> public static void getH3UnidirectionalEdgesFromHexagon(H3Index origin, List <H3Index> edges) { // Determine if the origin is a pentagon and special treatment needed. int isPentagon = H3Index.h3IsPentagon(origin); // This is actually quite simple. Just modify the bits of the origin // slightly for each direction, except the 'k' direction in pentagons, // which is zeroed. for (int i = 0; i < 6; i++) { if (isPentagon != 0 && i == 0) { edges[i] = H3Index.H3_INVALID_INDEX; } else { edges[i] = origin; var ei = edges[i]; H3Index.H3_SET_MODE(ref ei, Constants.H3_UNIEDGE_MODE); H3Index.H3_SET_RESERVED_BITS(ref ei, (ulong)i + 1); edges[i] = ei; } } }
/// <summary> /// Determines if the provided H3Index is a valid unidirectional edge index /// </summary> /// <param name="edge">The unidirectional edge H3Index</param> /// <returns>1 if it is a unidirectional edge H3Index, otherwise 0.</returns> /// <!-- Based off 3.1.1 --> public static int h3UnidirectionalEdgeIsValid(H3Index edge) { if (H3Index.H3_GET_MODE(ref edge) != Constants.H3_UNIEDGE_MODE) { return(0); } Direction neighborDirection = (Direction)H3Index.H3_GET_RESERVED_BITS(edge); if (neighborDirection <= Direction.CENTER_DIGIT || neighborDirection >= Direction.NUM_DIGITS) { return(0); } H3Index origin = getOriginH3IndexFromUnidirectionalEdge(edge); if (H3Index.h3IsPentagon(origin) != 0 && neighborDirection == Direction.K_AXES_DIGIT) { return(0); } return(H3Index.h3IsValid(origin)); }
/// <summary> /// Returns the hollow hexagonal ring centered at origin with sides of length k. /// </summary> /// <param name="origin">Origin location</param> /// <param name="k">k >= 0</param> /// <param name="out_hex">Array which must be of size 6 * k (or 1 if k == 0)</param> /// <returns>0 if no pentagonal distortion was encountered</returns> /// <!-- Based off 3.1.1 --> public static int hexRing(H3Index origin, int k, ref List <H3Index> out_hex) { // Short-circuit on 'identity' ring if (k == 0) { out_hex[0] = origin; return(0); } int idx = 0; // Number of 60 degree ccw rotations to perform on the direction (based on // which faces have been crossed.) int rotations = 0; // Scratch structure for checking for pentagons if (H3Index.h3IsPentagon(origin) > 0) { // Pentagon was encountered; bail out as user doesn't want this. return(Constants.HEX_RANGE_PENTAGON); } for (int ring = 0; ring < k; ring++) { origin = h3NeighborRotations(origin, Constants.NEXT_RING_DIRECTION, ref rotations); if (origin == 0) { // Should not be possible because `origin` would have to be a // pentagon return(Constants.HEX_RANGE_K_SUBSEQUENCE); // LCOV_EXCL_LINE } if (H3Index.h3IsPentagon(origin) > 0) { return(Constants.HEX_RANGE_PENTAGON); } } H3Index lastIndex = origin; out_hex[idx] = origin; idx++; for (int direction = 0; direction < 6; direction++) { for (int pos = 0; pos < k; pos++) { origin = h3NeighborRotations(origin, DIRECTIONS[direction], ref rotations); if (origin == 0) { // Should not be possible because `origin` would have to be a // pentagon return(Constants.HEX_RANGE_K_SUBSEQUENCE); // LCOV_EXCL_LINE } // Skip the very last index, it was already added. We do // however need to traverse to it because of the pentagonal // distortion check, below. if (pos != k - 1 || direction != 5) { out_hex[idx] = origin; idx++; if (H3Index.h3IsPentagon(origin) > 0) { return(Constants.HEX_RANGE_PENTAGON); } } } } // Check that this matches the expected lastIndex, if it doesn't, // it indicates pentagonal distortion occurred and we should report // failure. if (lastIndex != origin) { return(Constants.HEX_RANGE_PENTAGON); } else { return(Constants.HEX_RANGE_SUCCESS); } }
/// <summary> /// compact takes a set of hexagons all at the same resolution and compresses /// them by pruning full child branches to the parent level. This is also done /// for all parents recursively to get the minimum number of hex addresses that /// perfectly cover the defined space. /// </summary> /// <param name="h3Set"> Set of hexagons</param> /// <param name="compactedSet"> The output array of compressed hexagons (pre-allocated)</param> /// <param name="numHexes"> The size of the input and output arrays (possible that no /// contiguous regions exist in the set at all and no compression possible)</param> /// <returns>an error code on bad input data</returns> /// <remarks> /// We're going to modify this a little bit using some LINQ. /// </remarks> /// <!-- Based off 3.1.1 --> public static int compact(ref List <H3Index> h3Set, ref List <H3Index> compactedSet, int numHexes) { // Maximum resolution. We're out. int res = H3_GET_RESOLUTION(h3Set[0]); if (res == 0) { // No compaction possible, just copy the set to output compactedSet = new List <H3Index>(h3Set); return(0); } var realCompacted = new List <ulong>(); // Create a scratch list List <H3Index> scratchList = new List <H3Index>(numHexes); Dictionary <ulong, List <ulong> > generation = new Dictionary <ulong, List <ulong> >(); // These are ones we haven't processed. List <H3Index> remainingHexes = new List <H3Index>(h3Set.Take(numHexes)); // Loop through until we've removed the stragglers at each resolution // and eventually have gotten the biggest sized hexes possible stored away while (remainingHexes.Count > 0) { // What resolution are we looking at, and what resolution is our parent? res = H3_GET_RESOLUTION(remainingHexes[0]); int parentRes = res - 1; // Start processing our unprocessed and storing the parent and children in generation foreach (var hex in remainingHexes) { H3Index parent = h3ToParent(hex, parentRes); if (!generation.ContainsKey(parent.value)) { generation[parent.value] = new List <ulong>(); } if (generation[parent].Contains(hex.value)) { return(-1); // We have duplicate hexes that we're trying to compact } generation[parent].Add(hex.value); } // Only possible on duplicate input var errorTest = generation.Where(hex => hex.Value.Count > 7); if (errorTest.Any()) { return(-2); } remainingHexes.Clear(); var pentagons = generation.Where(parent => H3Index.h3IsPentagon(parent.Key) == 1); var pentaParents = pentagons.Select(keyValuePair => keyValuePair.Key) .Select(dummy => (H3Index)dummy).ToList(); foreach (var pentaParent in pentaParents) { if (generation[pentaParent].Count == 6) { remainingHexes.Add(pentaParent); } else { realCompacted.AddRange(generation[pentaParent]); } generation.Remove(pentaParent); } var orphans = generation.Where(hex => hex.Key != 0 && hex.Value.Count > 0 && hex.Value.Count < 7); realCompacted.AddRange(orphans.SelectMany(valuePair => valuePair.Value)); var nextgen = generation.Where(hex => hex.Value.Count == 7); remainingHexes.AddRange(nextgen.Select(valuePair => new H3Index(valuePair.Key)).ToList()); generation.Clear(); } compactedSet.Capacity = numHexes; compactedSet = realCompacted.Select(number => new H3Index(number)).ToList(); return(0); }
/// <summary> /// hexRange produces indexes within k distance of the origin index. /// Output behavior is undefined when one of the indexes returned by this /// function is a pentagon or is in the pentagon distortion area. /// /// k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and /// all neighboring indexes, and so on. /// /// Output is placed in the provided array in order of increasing distance from /// the origin. The distances in hexagons is placed in the distances array at /// the same offset. /// </summary> /// <param name="origin">Origin location</param> /// <param name="k">k >= 0</param> /// <param name="out_size">Array which must be of size <see cref="maxKringSize"/>(k)</param> /// <param name="distances">Null or array which must be of size <see cref="maxKringSize"/>(k)</param> /// <returns>0 if no pentagon or pentagonal distortion area was encountered.</returns> /// <!-- Based off 3.1.1 --> internal static int hexRangeDistances(H3Index origin, int k, ref List <H3Index> out_size, ref List <int> distances) { // Return codes: // 1 Pentagon was encountered // 2 Pentagon distortion (deleted k subsequence) was encountered // Pentagon being encountered is not itself a problem; really the deleted // k-subsequence is the problem, but for compatibility reasons we fail on // the pentagon. for (int m = 0; m < out_size.Count; m++) { //out_size.Add(0); distances.Add(0); } // k must be >= 0, so origin is always needed int idx = 0; out_size[idx] = origin; distances[0] = 0; idx++; if (H3Index.h3IsPentagon(origin) > 0) { // Pentagon was encountered; bail out as user doesn't want this. return(Constants.HEX_RANGE_PENTAGON); } // 0 < ring <= k, current ring int ring = 1; // 0 <= direction < 6, current side of the ring int direction = 0; // 0 <= i < ring, current position on the side of the ring int i = 0; // Number of 60 degree ccw rotations to perform on the direction (based on // which faces have been crossed.) int rotations = 0; while (ring <= k) { if (direction == 0 && i == 0) { // Not putting in the output set as it will be done later, at // the end of this ring. origin = h3NeighborRotations(origin, Constants.NEXT_RING_DIRECTION, ref rotations); if (origin == 0) { // Should not be possible because 'origin' would have to be a // pentagon return(Constants.HEX_RANGE_K_SUBSEQUENCE); } if (H3Index.h3IsPentagon(origin) > 0) { // Pentagon was encountered; bail out as user doesn't want this. return(Constants.HEX_RANGE_PENTAGON); } } origin = h3NeighborRotations(origin, DIRECTIONS[direction], ref rotations); if (origin == 0) { // Should not be possible because `origin` would have to be a // pentagon return(Constants.HEX_RANGE_K_SUBSEQUENCE); } out_size[idx] = origin; if (distances.Count > 0) { distances[idx] = ring; } idx++; i++; // Check if end of this side of the k-ring if (i == ring) { i = 0; direction++; // Check if end of this ring. if (direction == 6) { direction = 0; ring++; } } if (H3Index.h3IsPentagon(origin) > 0) { // Pentagon was encountered; bail out as user doesn't want this. return(Constants.HEX_RANGE_PENTAGON); } } return(Constants.HEX_RANGE_SUCCESS); }