@@ -569,6 +569,24 @@ final class HexRing {
569569 CoordIJK .Direction .CENTER_DIGIT ,
570570 CoordIJK .Direction .IJ_AXES_DIGIT } };
571571
572+ private static final CoordIJK .Direction [] NEIGHBORSETCLOCKWISE = new CoordIJK .Direction [] {
573+ CoordIJK .Direction .CENTER_DIGIT ,
574+ CoordIJK .Direction .JK_AXES_DIGIT ,
575+ CoordIJK .Direction .IJ_AXES_DIGIT ,
576+ CoordIJK .Direction .J_AXES_DIGIT ,
577+ CoordIJK .Direction .IK_AXES_DIGIT ,
578+ CoordIJK .Direction .K_AXES_DIGIT ,
579+ CoordIJK .Direction .I_AXES_DIGIT };
580+
581+ private static final CoordIJK .Direction [] NEIGHBORSETCOUNTERCLOCKWISE = new CoordIJK .Direction [] {
582+ CoordIJK .Direction .CENTER_DIGIT ,
583+ CoordIJK .Direction .IK_AXES_DIGIT ,
584+ CoordIJK .Direction .JK_AXES_DIGIT ,
585+ CoordIJK .Direction .K_AXES_DIGIT ,
586+ CoordIJK .Direction .IJ_AXES_DIGIT ,
587+ CoordIJK .Direction .I_AXES_DIGIT ,
588+ CoordIJK .Direction .J_AXES_DIGIT };
589+
572590 /**
573591 * Produce all neighboring cells. For Hexagons there will be 6 neighbors while
574592 * for pentagon just 5.
@@ -581,52 +599,115 @@ public static long[] hexRing(long origin) {
581599 int idx = 0 ;
582600 long previous = -1 ;
583601 for (int i = 0 ; i < 6 ; i ++) {
584- int [] rotations = new int [] { 0 };
585- long [] nextNeighbor = new long [] { 0 };
586- int neighborResult = h3NeighborRotations (origin , DIRECTIONS [i ].digit (), rotations , nextNeighbor );
587- if (neighborResult != E_PENTAGON ) {
588- // E_PENTAGON is an expected case when trying to traverse off of
589- // pentagons.
590- if (neighborResult != E_SUCCESS ) {
591- throw new IllegalArgumentException ();
592- }
593- if (previous != nextNeighbor [0 ]) {
594- out [idx ++] = nextNeighbor [0 ];
595- previous = nextNeighbor [0 ];
602+ long neighbor = h3NeighborInDirection (origin , DIRECTIONS [i ].digit ());
603+ if (neighbor != -1 ) {
604+ // -1 is an expected case when trying to traverse off of pentagons.
605+ if (previous != neighbor ) {
606+ out [idx ++] = neighbor ;
607+ previous = neighbor ;
596608 }
597609 }
598610 }
599611 assert idx == out .length ;
600612 return out ;
601613 }
602614
615+ /**
616+ * Returns whether or not the provided H3Indexes are neighbors.
617+ * @param origin The origin H3 index.
618+ * @param destination The destination H3 index.
619+ * @return true if the indexes are neighbors, false otherwise
620+ */
621+ public static boolean areNeighbours (long origin , long destination ) {
622+ // Make sure they're hexagon indexes
623+ if (H3Index .H3_get_mode (origin ) != Constants .H3_CELL_MODE ) {
624+ throw new IllegalArgumentException ("Invalid cell: " + origin );
625+ }
626+
627+ if (H3Index .H3_get_mode (destination ) != Constants .H3_CELL_MODE ) {
628+ throw new IllegalArgumentException ("Invalid cell: " + destination );
629+ }
630+
631+ // Hexagons cannot be neighbors with themselves
632+ if (origin == destination ) {
633+ return false ;
634+ }
635+
636+ final int resolution = H3Index .H3_get_resolution (origin );
637+ // Only hexagons in the same resolution can be neighbors
638+ if (resolution != H3Index .H3_get_resolution (destination )) {
639+ return false ;
640+ }
641+
642+ // H3 Indexes that share the same parent are very likely to be neighbors
643+ // Child 0 is neighbor with all of its parent's 'offspring', the other
644+ // children are neighbors with 3 of the 7 children. So a simple comparison
645+ // of origin and destination parents and then a lookup table of the children
646+ // is a super-cheap way to possibly determine they are neighbors.
647+ if (resolution > 1 ) {
648+ long originParent = H3 .h3ToParent (origin );
649+ long destinationParent = H3 .h3ToParent (destination );
650+ if (originParent == destinationParent ) {
651+ int originResDigit = H3Index .H3_get_index_digit (origin , resolution );
652+ int destinationResDigit = H3Index .H3_get_index_digit (destination , resolution );
653+ if (originResDigit == CoordIJK .Direction .CENTER_DIGIT .digit ()
654+ || destinationResDigit == CoordIJK .Direction .CENTER_DIGIT .digit ()) {
655+ return true ;
656+ }
657+ if (originResDigit >= CoordIJK .Direction .INVALID_DIGIT .digit ()) {
658+ // Prevent indexing off the end of the array below
659+ throw new IllegalArgumentException ("" );
660+ }
661+ if ((originResDigit == CoordIJK .Direction .K_AXES_DIGIT .digit ()
662+ || destinationResDigit == CoordIJK .Direction .K_AXES_DIGIT .digit ()) && H3 .isPentagon (originParent )) {
663+ // If these are invalid cells, fail rather than incorrectly
664+ // reporting neighbors. For pentagon cells that are actually
665+ // neighbors across the deleted subsequence, they will fail the
666+ // optimized check below, but they will be accepted by the
667+ // gridDisk check below that.
668+ throw new IllegalArgumentException ("Undefined error checking for neighbors" );
669+ }
670+ // These sets are the relevant neighbors in the clockwise
671+ // and counter-clockwise
672+ if (NEIGHBORSETCLOCKWISE [originResDigit ].digit () == destinationResDigit
673+ || NEIGHBORSETCOUNTERCLOCKWISE [originResDigit ].digit () == destinationResDigit ) {
674+ return true ;
675+ }
676+ }
677+ }
678+ // Otherwise, we have to determine the neighbor relationship the "hard" way.
679+ for (int i = 0 ; i < 6 ; i ++) {
680+ long neighbor = h3NeighborInDirection (origin , DIRECTIONS [i ].digit ());
681+ if (neighbor != -1 ) {
682+ // -1 is an expected case when trying to traverse off of
683+ // pentagons.
684+ if (destination == neighbor ) {
685+ return true ;
686+ }
687+ }
688+ }
689+ return false ;
690+ }
691+
603692 /**
604693 * Returns the hexagon index neighboring the origin, in the direction dir.
605694 *
606- * Implementation note: The only reachable case where this returns 0 is if the
695+ * Implementation note: The only reachable case where this returns -1 is if the
607696 * origin is a pentagon and the translation is in the k direction. Thus,
608- * 0 can only be returned if origin is a pentagon.
697+ * -1 can only be returned if origin is a pentagon.
609698 *
610699 * @param origin Origin index
611700 * @param dir Direction to move in
612- * @param rotations Number of ccw rotations to perform to reorient the
613- * translation vector. Will be modified to the new number of
614- * rotations to perform (such as when crossing a face edge.)
615- * @param out H3Index of the specified neighbor if succesful
616- * @return E_SUCCESS on success
701+ * @return H3Index of the specified neighbor or -1 if there is no more neighbor
617702 */
618- private static int h3NeighborRotations (long origin , int dir , int [] rotations , long [] out ) {
703+ private static long h3NeighborInDirection (long origin , int dir ) {
619704 long current = origin ;
620705
621- for (int i = 0 ; i < rotations [0 ]; i ++) {
622- dir = CoordIJK .rotate60ccw (dir );
623- }
624-
625706 int newRotations = 0 ;
626707 int oldBaseCell = H3Index .H3_get_base_cell (current );
627708 if (oldBaseCell < 0 || oldBaseCell >= Constants .NUM_BASE_CELLS ) { // LCOV_EXCL_BR_LINE
628709 // Base cells less than zero can not be represented in an index
629- return E_CELL_INVALID ;
710+ throw new IllegalArgumentException ( "Invalid base cell looking for neighbor" ) ;
630711 }
631712 int oldLeadingDigit = H3Index .h3LeadingNonZeroDigit (current );
632713
@@ -646,7 +727,6 @@ private static int h3NeighborRotations(long origin, int dir, int[] rotations, lo
646727 // perform the adjustment for the k-subsequence we're skipping
647728 // over.
648729 current = H3Index .h3Rotate60ccw (current );
649- rotations [0 ] = rotations [0 ] + 1 ;
650730 }
651731
652732 break ;
@@ -655,7 +735,7 @@ private static int h3NeighborRotations(long origin, int dir, int[] rotations, lo
655735 int nextDir ;
656736 if (oldDigit == CoordIJK .Direction .INVALID_DIGIT .digit ()) {
657737 // Only possible on invalid input
658- return E_CELL_INVALID ;
738+ throw new IllegalArgumentException () ;
659739 } else if (H3Index .isResolutionClassIII (r + 1 )) {
660740 current = H3Index .H3_set_index_digit (current , r + 1 , NEW_DIGIT_II [oldDigit ][dir ].digit ());
661741 nextDir = NEW_ADJUSTMENT_II [oldDigit ][dir ].digit ();
@@ -676,8 +756,6 @@ private static int h3NeighborRotations(long origin, int dir, int[] rotations, lo
676756
677757 int newBaseCell = H3Index .H3_get_base_cell (current );
678758 if (BaseCells .isBaseCellPentagon (newBaseCell )) {
679- boolean alreadyAdjustedKSubsequence = false ;
680-
681759 // force rotation out of missing k-axes sub-sequence
682760 if (H3Index .h3LeadingNonZeroDigit (current ) == CoordIJK .Direction .K_AXES_DIGIT .digit ()) {
683761 if (oldBaseCell != newBaseCell ) {
@@ -694,63 +772,38 @@ private static int h3NeighborRotations(long origin, int dir, int[] rotations, lo
694772 // unreachable.
695773 current = H3Index .h3Rotate60ccw (current ); // LCOV_EXCL_LINE
696774 }
697- alreadyAdjustedKSubsequence = true ;
698775 } else {
699776 // In this case, we traversed into the deleted
700777 // k subsequence from within the same pentagon
701778 // base cell.
702779 if (oldLeadingDigit == CoordIJK .Direction .CENTER_DIGIT .digit ()) {
703780 // Undefined: the k direction is deleted from here
704- return E_PENTAGON ;
781+ return - 1L ;
705782 } else if (oldLeadingDigit == CoordIJK .Direction .JK_AXES_DIGIT .digit ()) {
706783 // Rotate out of the deleted k subsequence
707784 // We also need an additional change to the direction we're
708785 // moving in
709786 current = H3Index .h3Rotate60ccw (current );
710- rotations [0 ] = rotations [0 ] + 1 ;
711787 } else if (oldLeadingDigit == CoordIJK .Direction .IK_AXES_DIGIT .digit ()) {
712788 // Rotate out of the deleted k subsequence
713789 // We also need an additional change to the direction we're
714790 // moving in
715791 current = H3Index .h3Rotate60cw (current );
716- rotations [0 ] = rotations [0 ] + 5 ;
717792 } else {
718793 // Should never occur
719- return E_FAILED ; // LCOV_EXCL_LINE
794+ throw new IllegalArgumentException ( "Undefined error looking for neighbor" ) ; // LCOV_EXCL_LINE
720795 }
721796 }
722797 }
723798
724- for (int i = 0 ; i < newRotations ; i ++)
799+ for (int i = 0 ; i < newRotations ; i ++) {
725800 current = H3Index .h3RotatePent60ccw (current );
726-
727- // Account for differing orientation of the base cells (this edge
728- // might not follow properties of some other edges.)
729- if (oldBaseCell != newBaseCell ) {
730- if (BaseCells .isBaseCellPolarPentagon (newBaseCell )) {
731- // 'polar' base cells behave differently because they have all
732- // i neighbors.
733- if (oldBaseCell != 118
734- && oldBaseCell != 8
735- && H3Index .h3LeadingNonZeroDigit (current ) != CoordIJK .Direction .JK_AXES_DIGIT .digit ()) {
736- rotations [0 ] = rotations [0 ] + 1 ;
737- }
738- } else if (H3Index .h3LeadingNonZeroDigit (current ) == CoordIJK .Direction .IK_AXES_DIGIT .digit ()
739- && alreadyAdjustedKSubsequence == false ) {
740- // account for distortion introduced to the 5 neighbor by the
741- // deleted k subsequence.
742- rotations [0 ] = rotations [0 ] + 1 ;
743- }
744801 }
745802 } else {
746803 for (int i = 0 ; i < newRotations ; i ++)
747804 current = H3Index .h3Rotate60ccw (current );
748805 }
749-
750- rotations [0 ] = (rotations [0 ] + newRotations ) % 6 ;
751- out [0 ] = current ;
752-
753- return E_SUCCESS ;
806+ return current ;
754807 }
755808
756809}
0 commit comments