├── Version.txt ├── h3net.300.png ├── Tests ├── NUnit │ └── H3Suite │ │ ├── Readme.md │ │ ├── TestH3Line.cs │ │ ├── license.txt │ │ ├── TestMaxH3ToChildrenSize.cs │ │ ├── TestBaseCells.cs │ │ ├── TestH3ToParent.cs │ │ ├── TestCoordIjk.cs │ │ ├── TestPentagonIndexes.cs │ │ ├── TestCoordIj.cs │ │ ├── TestVec2d.cs │ │ ├── H3Suite.csproj │ │ ├── TestVec3d.cs │ │ ├── TestH3ToCenterChild.cs │ │ ├── TestH3CellArea.cs │ │ ├── TestLinkedGeo.cs │ │ ├── TestH3DistanceExhaustive.cs │ │ ├── TestVertex.cs │ │ ├── TestHexRanges.cs │ │ ├── TestH3SetToVertexGraph.cs │ │ ├── TestH3LineExhaustive.cs │ │ ├── Lib │ │ └── Utility.cs │ │ ├── TestH3Distance.cs │ │ ├── TestH3UniEdgeExhaustive.cs │ │ ├── TestH3GetFaces.cs │ │ ├── TestH3ToChildren.cs │ │ ├── TestH3Api.cs │ │ └── TestHexRing.cs └── Polyfill │ └── Polyfill.csproj ├── H3Lib ├── H3Mode.cs ├── Overage.cs ├── GeoMultiPolygon.cs ├── GeoPolygon.cs ├── Documentation │ ├── readme.md │ ├── Uber-Api-Indexing.md │ ├── Uber-Api.md │ ├── Uber-Api-Region.md │ ├── Uber-Api-Hierarchical-Grid.md │ ├── Uber-Api-Index-Inspection.md │ └── Uber-Api-Unidirectional-Edge.md ├── license.txt ├── GeoFence.cs ├── GeoBoundary.cs ├── LinkedGeoCoord.cs ├── Direction.cs ├── PentagonDirectionFace.cs ├── Extensions │ ├── VertexGraphExtensions.cs │ ├── Vec3dExtensions.cs │ ├── Readme.md │ ├── DirectionExtensions.cs │ ├── CoordIjExtensions.cs │ └── BaseCellsExtensions.cs ├── H3Lib.csproj ├── Readme.md ├── VertexNode.cs ├── BaseCellRotation.cs ├── Vec3d.cs ├── FaceIjk.cs ├── FaceOrientIjk.cs ├── BBox.cs ├── CoordIj.cs ├── BaseCellData.cs ├── LinkedGeoLoop.cs ├── Vec2d.cs ├── CoordIjk.cs └── LinkedGeoPolygon.cs ├── Apps ├── AppsLib │ ├── AppsLib.csproj │ └── Utility.cs └── Filters │ ├── KRing │ ├── KRing.csproj │ └── Program.cs │ ├── GeoToH3 │ ├── GeoToH3.csproj │ └── Program.cs │ ├── H3ToLocalIj │ ├── H3ToLocalIj.csproj │ └── Program.cs │ ├── LocalIjToH3 │ ├── LocalIjToH3.csproj │ └── Program.cs │ ├── H3ToGeo │ ├── H3ToGeo.csproj │ └── Program.cs │ ├── HexRange │ ├── HexRange.csproj │ └── Program.cs │ ├── H3ToComponents │ ├── H3ToComponents.csproj │ └── Program.cs │ └── H3ToGeoBoundary │ ├── H3ToGeoBoundary.csproj │ └── Program.cs ├── .github └── workflows │ ├── dotnet.yml │ └── codeql-analysis.yml ├── h3net.sln.DotSettings ├── .gitattributes ├── OldApi.md ├── Credits.md ├── CODE_OF_CONDUCT.md ├── Readme.md └── .gitignore /Version.txt: -------------------------------------------------------------------------------- 1 | 3.7.1 2 | -------------------------------------------------------------------------------- /h3net.300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RichardVasquez/h3net/HEAD/h3net.300.png -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/Readme.md: -------------------------------------------------------------------------------- 1 | # NUnit Tests 2 | 3 | The base test suites converted to NUnit functionality. 4 | 5 | As of this push, all 224 tests pass via NUnit test runner on JetBrains Rider. 6 | -------------------------------------------------------------------------------- /Tests/Polyfill/Polyfill.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /H3Lib/H3Mode.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib 2 | { 3 | /// 4 | /// mode for any examined H3Index 5 | /// 6 | public enum H3Mode 7 | { 8 | /// 9 | /// Hexagon mode 10 | /// 11 | Hexagon = 1, 12 | /// 13 | /// Directed Edge mode 14 | /// 15 | UniEdge = 2 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Apps/AppsLib/AppsLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | Debug;Release 6 | AnyCPU 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3Line.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestH3Line 9 | { 10 | [Test] 11 | public void H3LineAcrossMultipleFaces() 12 | { 13 | H3Index start = 0x85285aa7fffffff; 14 | H3Index end = 0x851d9b1bfffffff; 15 | 16 | int lineSz = start.LineSize(end); 17 | Assert.Less(lineSz, 0); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /H3Lib/Overage.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib 2 | { 3 | /// 4 | /// Digit representing overage type 5 | /// 6 | public enum Overage 7 | { 8 | /// 9 | /// No overage 10 | /// 11 | NO_OVERAGE = 0, 12 | /// 13 | /// Overage at face edge 14 | /// 15 | FACE_EDGE = 1, 16 | /// 17 | /// Overage goes on next face 18 | /// 19 | NEW_FACE = 2 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Apps/Filters/KRing/KRing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Apps/Filters/GeoToH3/GeoToH3.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /H3Lib/GeoMultiPolygon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// Simplified core of GeoJSON MultiPolygon coordinates definition 7 | /// 8 | public class GeoMultiPolygon 9 | { 10 | /// 11 | /// Number of elements in the array pointed to by the holes 12 | /// 13 | public int NumPolygons; 14 | /// 15 | /// interior boundaries (holes) in the polygon 16 | /// 17 | public List Polygons; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToLocalIj/H3ToLocalIj.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Apps/Filters/LocalIjToH3/LocalIjToH3.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ Primary, 3.7.2 ] 6 | pull_request: 7 | branches: [ Primary, 3.7.2 ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 5.x 20 | - name: Restore dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --no-restore 24 | - name: Test 25 | run: dotnet test --no-build --verbosity normal 26 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToGeo/H3ToGeo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Apps/Filters/HexRange/HexRange.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToComponents/H3ToComponents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToGeoBoundary/H3ToGeoBoundary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /H3Lib/GeoPolygon.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// Simplified core of GeoJSON Polygon coordinates definition 7 | /// 8 | public class GeoPolygon 9 | { 10 | /// 11 | /// exterior boundary of the polygon 12 | /// 13 | public GeoFence GeoFence; 14 | /// 15 | /// Number of elements in the array pointed to by the holes 16 | /// 17 | public int NumHoles; 18 | /// 19 | /// interior boundaries (holes) in the polygon 20 | /// 21 | public List Holes; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /H3Lib/Documentation/readme.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | If you want to dive right in, the implementation of 4 | the Uber H3 API in h3net can be found at [Uber H3 API](Uber-Api.md). 5 | 6 | Or, you can read [the auto generated docs](H3Lib.md) and work out how 7 | the internals work, though that's subject to change before release. 8 | 9 | While this is still being built, I'm running an auto build of documentation. 10 | Later, I'll clean it up, but this will give you an idea as to where it's at, 11 | at any given moment. 12 | 13 | Currently, this is using XML-Documentation that gets turned into one big XML 14 | file, then it's auto converted to a MarkDown file via 15 | [lijunle/Vsxmd](https://github.com/lijunle/Vsxmd) 16 | 17 | Later in the project, I'll start editing the structure of that into bite 18 | sized chunks. 19 | -------------------------------------------------------------------------------- /H3Lib/license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018-2021, Richard Vasquez 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ----------------------------------------------------------- 16 | 17 | Original version written in C, Copyright 2016-2020 Uber Technologies, Inc. 18 | C version licensed under the Apache License, Version 2.0 (the "License"); 19 | Original C Source code available at: https://github.com/uber/h3 20 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018-2021, Richard Vasquez 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | ----------------------------------------------------------- 16 | 17 | Original version written in C, Copyright 2016-2020 Uber Technologies, Inc. 18 | C version licensed under the Apache License, Version 2.0 (the "License"); 19 | Original Source code available at: https://github.com/uber/h3 20 | -------------------------------------------------------------------------------- /H3Lib/GeoFence.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib 2 | { 3 | /// 4 | /// similar to GeoBoundary, but requires more alloc work 5 | /// 6 | public class GeoFence 7 | { 8 | /// 9 | /// number of vertices 10 | /// 11 | public int NumVerts; 12 | /// 13 | /// vertices in ccw order 14 | /// 15 | public GeoCoord[] Verts; 16 | 17 | /// 18 | /// Indicates if the geofence has no vertices 19 | /// 20 | public bool IsEmpty => NumVerts == 0; 21 | 22 | /// 23 | /// constructor 24 | /// 25 | public GeoFence() 26 | { 27 | Verts = new[] 28 | { 29 | new GeoCoord(0.0m,0.0m), new GeoCoord(0.0m,0.0m) 30 | }; 31 | NumVerts = 0; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Apps/AppsLib/Utility.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | 5 | namespace AppsLib 6 | { 7 | public static class Utility 8 | { 9 | public static string GeoToStringDegsNoFmt(GeoCoord gc) 10 | { 11 | var sb = new StringBuilder(); 12 | //,9:66 13 | sb.Append($"({gc.Latitude.RadiansToDegrees(),9:F6},{gc.Longitude.RadiansToDegrees(),9:F6})"); 14 | return sb.ToString(); 15 | } 16 | 17 | public static string GeoBoundaryPrintLines(GeoBoundary gb) 18 | { 19 | var sb = new StringBuilder(); 20 | 21 | sb.AppendLine("{"); 22 | for (int i = 0;i < gb.NumVerts; i++) 23 | { 24 | sb.Append(" "); 25 | sb.AppendLine(GeoToStringDegsNoFmt(gb.Verts[i])); 26 | } 27 | 28 | sb.AppendLine("}"); 29 | return sb.ToString(); 30 | 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestMaxH3ToChildrenSize.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestMaxH3ToChildrenSize 9 | { 10 | private GeoCoord sf = new GeoCoord(0.659966917655m, 2 * 3.14159m - 2.1364398519396m); 11 | 12 | [Test] 13 | public void MaxH3ToChildrenSize() 14 | { 15 | var parent = sf.ToH3Index(7); 16 | 17 | Assert.AreEqual(0, parent.MaxChildrenSize(3)); 18 | Assert.AreEqual(1, parent.MaxChildrenSize(7)); 19 | Assert.AreEqual(7, parent.MaxChildrenSize(8)); 20 | Assert.AreEqual(49, parent.MaxChildrenSize(9)); 21 | } 22 | 23 | [Test] 24 | public void maxH3ToChildrenSize_largest() 25 | { 26 | // write out the types explicitly, to make sure errors don't go 27 | // undetected to to type casting. 28 | 29 | H3Index h = 0x806dfffffffffff; // res 0 *hexagon* 30 | ulong expected = 4747561509943L; // 7^15 31 | long outCount = h.MaxChildrenSize(15); 32 | Assert.AreEqual(expected, (ulong) outCount); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestBaseCells.cs: -------------------------------------------------------------------------------- 1 | using H3Lib.Extensions; 2 | using NUnit.Framework; 3 | 4 | namespace TestSuite 5 | { 6 | [TestFixture] 7 | public class TestBaseCells 8 | { 9 | [Test] 10 | public void GetRes0Indexes() 11 | { 12 | var indexes = BaseCellsExtensions.GetRes0Indexes(); 13 | Assert.AreEqual(indexes[0].Value, 0x8001fffffffffffUL); 14 | Assert.AreEqual(indexes[121].Value, 0x80f3fffffffffffUL); 15 | } 16 | 17 | [Test] 18 | public void BaseCellToCcwRot60() 19 | { 20 | // a few random spot-checks 21 | Assert.AreEqual(16.ToCounterClockwiseRotate60(0), 0); 22 | Assert.AreEqual(32.ToCounterClockwiseRotate60(0), 3); 23 | Assert.AreEqual(7.ToCounterClockwiseRotate60(3), 1); 24 | } 25 | 26 | [Test] 27 | public void BaseCellToCcwRot60Invalid() 28 | { 29 | Assert.AreEqual(16.ToCounterClockwiseRotate60(42), H3Lib.Constants.BaseCells.InvalidRotations); 30 | Assert.AreEqual(16.ToCounterClockwiseRotate60(-1), H3Lib.Constants.BaseCells.InvalidRotations); 31 | Assert.AreEqual(11.ToCounterClockwiseRotate60(0), H3Lib.Constants.BaseCells.InvalidRotations); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3ToParent.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestH3ToParent 9 | { 10 | public GeoCoord sf = new GeoCoord(0.659966917655m, 2 * 3.14159m - 2.1364398519396m); 11 | 12 | [Test] 13 | public void AncestorsForEachRes() 14 | { 15 | H3Index child; 16 | H3Index comparisonParent; 17 | H3Index parent; 18 | 19 | for (int res = 1; res < 15; res++) 20 | { 21 | for (int step = 0; step < res; step++) 22 | { 23 | child = sf.ToH3Index(res); 24 | parent = child.ToParent(res - step); 25 | comparisonParent = sf.ToH3Index(res - step); 26 | Assert.AreEqual(parent, comparisonParent); 27 | } 28 | } 29 | } 30 | 31 | [Test] 32 | public void invalidInputs() 33 | { 34 | var child = sf.ToH3Index(5); 35 | 36 | Assert.AreEqual(0, child.ToParent(6).Value); 37 | Assert.AreEqual(0, child.ToParent(-1).Value); 38 | Assert.AreEqual(0, child.ToParent(15).Value); 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /H3Lib/GeoBoundary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | 4 | namespace H3Lib 5 | { 6 | /// 7 | /// cell boundary in latitude/longitude 8 | /// 9 | public class GeoBoundary 10 | { 11 | /// 12 | /// number of vertices 13 | /// 14 | public int NumVerts; 15 | 16 | /// 17 | /// vertices in ccw order 18 | /// 19 | public readonly List Verts = new List(); 20 | 21 | /// 22 | /// Constructor 23 | /// 24 | public GeoBoundary() 25 | { 26 | for (var i = 0; i < Constants.H3.MAX_CELL_BNDRY_VERTS; i++) 27 | { 28 | Verts.Add(new GeoCoord()); 29 | } 30 | } 31 | 32 | /// 33 | /// Debug information in string form 34 | /// 35 | public override string ToString() 36 | { 37 | var sb = new StringBuilder(); 38 | sb.Append($"GeoBoundary: {NumVerts} Vertices "); 39 | for (var i = 0; i < NumVerts; i++) 40 | { 41 | sb.Append($"- {i} {Verts[i]}"); 42 | } 43 | 44 | return sb.ToString(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestCoordIjk.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestCoordIjk 9 | { 10 | [Test] 11 | public void UnitIjkToDigit() 12 | { 13 | var zero = new CoordIjk(); 14 | var i = new CoordIjk(1, 0, 0); 15 | var outOfRange = new CoordIjk(2, 0, 0); 16 | var unNormalizedZero = new CoordIjk(2, 2, 2); 17 | 18 | Assert.AreEqual(zero.ToDirection(), Direction.CENTER_DIGIT); 19 | Assert.AreEqual(i.ToDirection(), Direction.I_AXES_DIGIT); 20 | Assert.AreEqual(outOfRange.ToDirection(), Direction.INVALID_DIGIT); 21 | Assert.AreEqual(unNormalizedZero.ToDirection(), Direction.CENTER_DIGIT); 22 | } 23 | 24 | [Test] 25 | public void Neighbor() 26 | { 27 | var ijk = new CoordIjk(); 28 | var zero = new CoordIjk(); 29 | var i = new CoordIjk(1, 0, 0); 30 | 31 | ijk = ijk.Neighbor(Direction.CENTER_DIGIT); 32 | Assert.AreEqual(ijk, zero); 33 | 34 | ijk = ijk.Neighbor(Direction.I_AXES_DIGIT); 35 | Assert.AreEqual(ijk, i); 36 | 37 | ijk = ijk.Neighbor(Direction.INVALID_DIGIT); 38 | Assert.AreEqual(ijk, i); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /H3Lib/LinkedGeoCoord.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// A wrapper class for storing GeoCoords within a linked list 7 | /// 8 | [DebuggerDisplay("Lat: {Latitude} Lon: {Longitude}")] 9 | public class LinkedGeoCoord 10 | { 11 | /// 12 | /// Vertex being held 13 | /// 14 | private readonly GeoCoord _gc; 15 | 16 | /// 17 | /// Latitude of vertex 18 | /// 19 | public decimal Latitude => _gc.Latitude; 20 | /// 21 | /// longitude of vertex 22 | /// 23 | public decimal Longitude => _gc.Longitude; 24 | 25 | /// 26 | /// Return the actual vertex, read only 27 | /// 28 | public GeoCoord Vertex => _gc; 29 | 30 | /// 31 | /// constructor 32 | /// 33 | public LinkedGeoCoord() 34 | { 35 | _gc = default; 36 | } 37 | 38 | /// 39 | /// constructor with vertex 40 | /// 41 | public LinkedGeoCoord(GeoCoord gc) 42 | { 43 | _gc = gc; 44 | } 45 | 46 | /// 47 | /// mutator to change vertex 48 | /// 49 | public LinkedGeoCoord Replacement(GeoCoord gc) 50 | { 51 | return new LinkedGeoCoord(gc); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestPentagonIndexes.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestPentagonIndexes 9 | { 10 | 11 | private const int PADDED_COUNT = 16; 12 | 13 | [Test] 14 | public void propertyTests() 15 | { 16 | int expectedCount = H3Index.PentagonIndexCount; 17 | 18 | for (int res = 0; res <= 15; res++) 19 | { 20 | 21 | //var h3Indexes = new H3Index[PADDED_COUNT]; 22 | var h3Indexes = res.GetPentagonIndexes(); 23 | 24 | int numFound = 0; 25 | 26 | for (int i = 0; i < h3Indexes.Count; i++) 27 | { 28 | H3Index h3Index = h3Indexes[i]; 29 | if (h3Index != 0) 30 | { 31 | numFound++; 32 | Assert.IsTrue(h3Index.IsValid()); 33 | Assert.IsTrue(h3Index.IsPentagon()); 34 | Assert.AreEqual(res, h3Index.Resolution); 35 | 36 | 37 | // verify uniqueness 38 | for (int j = i + 1; j < h3Indexes.Count; j++) 39 | { 40 | Assert.AreNotEqual(h3Index, h3Indexes[j]); 41 | } 42 | } 43 | } 44 | Assert.AreEqual(expectedCount, numFound); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestCoordIj.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestCoordIj 9 | { 10 | [Test] 11 | public void IjkToIj() 12 | { 13 | var ijk = new CoordIjk(); 14 | var ij = ijk.ToIj(); 15 | Assert.AreEqual(0, ij.I); 16 | Assert.AreEqual(0, ij.J); 17 | 18 | ijk = ij.ToIjk(); 19 | Assert.AreEqual(0, ijk.I); 20 | Assert.AreEqual(0, ijk.J); 21 | Assert.AreEqual(0, ijk.K); 22 | } 23 | 24 | [Test] 25 | public void IjkToIjRoundtrip() 26 | { 27 | for (Direction dir = Direction.CENTER_DIGIT; dir < Direction.NUM_DIGITS; dir++) 28 | { 29 | var ijk = new CoordIjk(); 30 | ijk = ijk.Neighbor(dir); 31 | 32 | var ij = ijk.ToIj(); 33 | 34 | var recovered = ij.ToIjk(); 35 | 36 | Assert.AreEqual(ijk, recovered); 37 | } 38 | } 39 | 40 | [Test] 41 | public void IjkToCubeRoundtrip() 42 | { 43 | for (Direction dir = Direction.CENTER_DIGIT; dir < Direction.NUM_DIGITS; dir++) 44 | { 45 | var ijk = new CoordIjk().Neighbor(dir); 46 | var original = ijk; 47 | 48 | ijk = ijk.ToCube(); 49 | ijk = ijk.FromCube(); 50 | 51 | Assert.AreEqual(ijk, original); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /H3Lib/Direction.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib 2 | { 3 | /// 4 | /// H3 digit representing ijk+ axes direction. 5 | /// Values will be within the lowest 3 bits of an integer. 6 | /// 7 | public enum Direction 8 | { 9 | /// 10 | /// H3 digit in center 11 | /// 12 | CENTER_DIGIT = 0, 13 | 14 | /// 15 | /// H3 digit in k-axes direction 16 | /// 17 | K_AXES_DIGIT = 1, 18 | 19 | /// 20 | /// H3 digit in j-axes direction 21 | /// 22 | J_AXES_DIGIT = 2, 23 | 24 | /// 25 | /// H3 digit in j==k direction 26 | /// 27 | JK_AXES_DIGIT = J_AXES_DIGIT | K_AXES_DIGIT, 28 | 29 | /// 30 | /// H3 digit in i-axes direction 31 | /// 32 | I_AXES_DIGIT = 4, 33 | 34 | /// 35 | /// H3 digit in i==k direction 36 | /// 37 | IK_AXES_DIGIT = I_AXES_DIGIT | K_AXES_DIGIT, 38 | 39 | /// 40 | /// H3 digit in i==j direction 41 | /// 42 | IJ_AXES_DIGIT = I_AXES_DIGIT | J_AXES_DIGIT, 43 | 44 | /// 45 | /// H3 digit in the invalid direction 46 | /// 47 | INVALID_DIGIT = 7, 48 | 49 | /// 50 | /// Valid digits will be less than this value. Same value as 51 | /// 52 | NUM_DIGITS = INVALID_DIGIT 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestVec2d.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using H3Lib; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestVec2d 9 | { 10 | [Test] 11 | public void V2DMagnitude() 12 | { 13 | var v = new Vec2d(3.0m, 4.0m); 14 | const decimal expected = 5.0m; 15 | decimal mag = v.Magnitude; 16 | Assert.IsTrue(Math.Abs(mag-expected) < Constants.H3.DBL_EPSILON); 17 | } 18 | 19 | [Test] 20 | public void V2DIntersect() 21 | { 22 | var p0 = new Vec2d(2.0m, 2.0m); 23 | var p1 = new Vec2d(6.0m, 6.0m); 24 | var p2 = new Vec2d(0.0m, 4.0m); 25 | var p3 = new Vec2d(10.0m, 4.0m); 26 | 27 | var intersection = Vec2d.FindIntersection(p0, p1, p2, p3); 28 | 29 | const decimal expectedX = 4.0m; 30 | const decimal expectedY = 4.0m; 31 | 32 | Assert.IsTrue(Math.Abs(intersection.X - expectedX) < Constants.H3.DBL_EPSILON); 33 | Assert.IsTrue(Math.Abs(intersection.Y - expectedY) < Constants.H3.DBL_EPSILON); 34 | } 35 | 36 | [Test] 37 | public void V2DEquals() 38 | { 39 | Vec2d v1 = new Vec2d(3.0m, 4.0m); 40 | Vec2d v2 = new Vec2d(3.0m, 4.0m); 41 | Vec2d v3 = new Vec2d(3.5m, 4.0m); 42 | Vec2d v4 = new Vec2d(3.0m, 4.5m); 43 | 44 | Assert.AreEqual(v1, v2); 45 | Assert.AreNotEqual(v1, v3); 46 | Assert.AreNotEqual(v1, v4); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /H3Lib/PentagonDirectionFace.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace H3Lib 5 | { 6 | /// 7 | /// The faces in each axial direction of a given pentagon base cell 8 | /// 9 | public readonly struct PentagonDirectionFace 10 | { 11 | /// 12 | /// base cell number 13 | /// 14 | public readonly int BaseCell; 15 | /// 16 | /// face numbers for each axial direction, in order, starting with J 17 | /// 18 | public readonly int[] Faces; 19 | 20 | /// 21 | /// Constructor 22 | /// 23 | /// 24 | /// 25 | public PentagonDirectionFace(int bc, IList faces) 26 | { 27 | BaseCell = bc; 28 | Faces = faces.Take(Constants.H3.NUM_PENT_VERTS).ToArray(); 29 | } 30 | 31 | /// 32 | /// Constructor 33 | /// 34 | /// 35 | public PentagonDirectionFace(IList raw) 36 | { 37 | BaseCell = raw[0]; 38 | Faces = raw.Skip(1).Take(Constants.H3.NUM_PENT_VERTS).ToArray(); 39 | } 40 | 41 | /// 42 | /// Constructor 43 | /// 44 | public PentagonDirectionFace(int bc, int f1, int f2, int f3, int f4, int f5) 45 | { 46 | BaseCell = bc; 47 | Faces = new[] {f1, f2, f3, f4, f5}; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/H3Suite.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0;netstandard2.1 5 | 8.0 6 | false 7 | TestSuite 8 | TestSuite 9 | Richard Vasquez 10 | A collection of NUnit tests for h3net 11 | 2018-2021, Richard 12 | https://github.com/RichardVasquez/h3net 13 | https://www.apache.org/licenses/LICENSE-2.0 14 | https://github.com/RichardVasquez/h3net 15 | true 16 | Debug;Release 17 | AnyCPU 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /H3Lib/Extensions/VertexGraphExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib.Extensions 2 | { 3 | /// 4 | /// Operations on VertexGraph type 5 | /// 6 | public static class VertexGraphExtensions 7 | { 8 | /// 9 | /// Internal: Create a LinkedGeoPolygon from a vertex graph. It is the 10 | /// responsibility of the caller to call destroyLinkedPolygon on the populated 11 | /// linked geo structure, or the memory for that structure will not be freed. 12 | /// 13 | /// Input graph 14 | /// Output polygon 15 | /// 19 | public static LinkedGeoPolygon ToLinkedGeoPolygon(this VertexGraph graph) 20 | { 21 | var result = new LinkedGeoPolygon(); 22 | 23 | while (graph.Count>0) 24 | { 25 | var edge = graph.FirstNode(); 26 | var loop = new LinkedGeoLoop(); 27 | while (edge.HasValue) 28 | { 29 | loop.AddLinkedCoord(edge.Value.From); 30 | //loop.GeoCoordList.AddLast(edge.Value.From); 31 | var nextVertex = edge.Value.To; 32 | graph.RemoveNode(edge.Value); 33 | edge = graph.FindVertex(nextVertex); 34 | } 35 | 36 | if (loop.Count > 0) 37 | { 38 | result.AddLinkedLoop(loop); 39 | } 40 | } 41 | 42 | return result; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestVec3d.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | using NUnit.Framework; 5 | 6 | namespace TestSuite 7 | { 8 | [TestFixture] 9 | public class TestVec3d 10 | { 11 | [Test] 12 | public void PointSquareDistance() 13 | { 14 | 15 | Vec3d v1 = new Vec3d(0, 0, 0); 16 | Vec3d v2 = new Vec3d(1, 0, 0); 17 | Vec3d v3 = new Vec3d(0, 1, 1); 18 | Vec3d v4 = new Vec3d(1, 1, 1); 19 | Vec3d v5 = new Vec3d(1, 1, 2); 20 | 21 | Assert.IsTrue(Math.Abs(v1.PointSquareDistance(v1)) < Constants.H3.DBL_EPSILON); 22 | Assert.IsTrue(Math.Abs(v1.PointSquareDistance(v2) - 1) < Constants.H3.DBL_EPSILON); 23 | Assert.IsTrue(Math.Abs(v1.PointSquareDistance(v3) - 2) < Constants.H3.DBL_EPSILON); 24 | Assert.IsTrue(Math.Abs(v1.PointSquareDistance(v4) - 3) < Constants.H3.DBL_EPSILON); 25 | Assert.IsTrue(Math.Abs(v1.PointSquareDistance(v5) - 6) < Constants.H3.DBL_EPSILON); 26 | } 27 | 28 | [Test] 29 | public void GeoToVec3d() 30 | { 31 | var origin = new Vec3d(); 32 | var c1 = new GeoCoord(); 33 | var p1 = c1.ToVec3d(); 34 | Assert.IsTrue(Math.Abs(origin.PointSquareDistance(p1) -1) < Constants.H3.EPSILON_RAD); 35 | 36 | var c2 = new GeoCoord(Constants.H3.M_PI_2, 0); 37 | var p2 = c2.ToVec3d(); 38 | Assert.IsTrue(Math.Abs(p1.PointSquareDistance(p2) - 2) < Constants.H3.EPSILON_RAD); 39 | 40 | var c3 = new GeoCoord(Constants.H3.M_PI, 0); 41 | var p3 = c3.ToVec3d(); 42 | Assert.IsTrue(Math.Abs(p1.PointSquareDistance(p3) - 4) 2 | 3 | 4 | net5.0;netstandard2.0;netstandard2.1 5 | 3.7.2 6 | 7.3 7 | H3 Library 8 | Richard Vasquez 9 | H3 Api Library 10 | 2018-2021, RIchard Vasquez 11 | https://github.com/RichardVasquez/h3net 12 | https://www.apache.org/licenses/LICENSE-2.0 13 | https://github.com/RichardVasquez/h3net 14 | git 15 | h3 hexagon spatial-indexing geospatial 16 | H3Net is a geospatial indexing system using hexagonal grid that can be (approximately) subdivided into finer and finer hexagonal grids, combining the benefits of a hexagonal grid with S2 hierarchical subdivisions, mostly translated from the original C code from Uber's H3 project. 17 | Richard Vasquez 18 | 3.7.1 19 | 3.7.1 20 | Debug;Release 21 | AnyCPU 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Apps/Filters/LocalIjToH3/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using CommandLineParser.Arguments; 5 | using H3Lib; 6 | using H3Lib.Extensions; 7 | 8 | namespace LocalIjToH3 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | var parser = 15 | new CommandLineParser.CommandLineParser(); 16 | 17 | args = args.Select(s => s.ToLower()).ToArray(); 18 | 19 | try 20 | { 21 | var argParser = new HexRangeArguments(); 22 | parser.ExtractArgumentAttributes(argParser); 23 | parser.ParseCommandLine(args); 24 | ProcessArguments(argParser); 25 | } 26 | catch (Exception) 27 | { 28 | Console.WriteLine("Unable to parse input."); 29 | parser.ShowUsage(); 30 | } 31 | } 32 | 33 | private static void ProcessArguments(HexRangeArguments argParser) 34 | { 35 | var origin = new H3Index(argParser.Origin); 36 | 37 | var ij = new CoordIj(argParser.I, argParser.J); 38 | 39 | var (status, cell) = ij.ToH3Experimental(origin); 40 | 41 | Console.WriteLine 42 | ( 43 | status != 0 44 | ? "NA" 45 | : cell.ToString() 46 | ); 47 | } 48 | } 49 | 50 | public class HexRangeArguments 51 | { 52 | [BoundedValueArgument(typeof (int),'i', Optional = false, Description = "I index")] 53 | public int I; 54 | 55 | [BoundedValueArgument(typeof(int), 'j', Optional = false, Description = "J index")] 56 | public int J; 57 | 58 | [BoundedValueArgument(typeof(ulong), 'o', "origin", Optional = false)] 59 | public ulong Origin; 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3ToCenterChild.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestH3ToCenterChild 9 | { 10 | private H3Index baseHex; 11 | GeoCoord baseCentroid; 12 | 13 | [SetUp] 14 | public void Init() 15 | { 16 | baseHex = new H3Index(8, 4, 2); 17 | baseCentroid = baseHex.ToGeoCoord(); 18 | } 19 | 20 | [Test] 21 | public void PropertyTests() 22 | { 23 | for (int res = 0; res <= Constants.H3.MAX_H3_RES - 1; res++) 24 | { 25 | for (int childRes = res + 1; childRes <= Constants.H3.MAX_H3_RES; childRes++) 26 | { 27 | var h3 = baseCentroid.ToH3Index(res); 28 | var centroid = h3.ToGeoCoord(); 29 | 30 | var geoChild = centroid.ToH3Index(childRes); 31 | var centerChild = h3.ToCenterChild(childRes); 32 | 33 | Assert.AreEqual(centerChild,geoChild); 34 | 35 | Assert.AreEqual(childRes, centerChild.Resolution); 36 | 37 | Assert.AreEqual(h3, centerChild.ToParent(res)); 38 | } 39 | } 40 | } 41 | 42 | [Test] 43 | public void SameRes() 44 | { 45 | int res = baseHex.Resolution; 46 | Assert.AreEqual(baseHex, baseHex.ToCenterChild(res)); 47 | } 48 | 49 | [Test] 50 | public void InvalidInputs() 51 | { 52 | int res = baseHex.Resolution; 53 | Assert.AreEqual(0, baseHex.ToCenterChild(res - 1).Value); 54 | Assert.AreEqual(0, baseHex.ToCenterChild(-1).Value); 55 | Assert.AreEqual(0, baseHex.ToCenterChild(Constants.H3.MAX_H3_RES + 1).Value); 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /H3Lib/Extensions/Vec3dExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib.Extensions 2 | { 3 | /// 4 | /// Operations on Vec3d 5 | /// 6 | public static class Vec3dExtensions 7 | { 8 | /// 9 | /// Calculate the square of the distance between two 3D coordinates. 10 | /// 11 | /// The first 3D coordinate. 12 | /// The second 3D coordinate. 13 | /// The square of the distance between the given points. 14 | /// 18 | internal static decimal PointSquareDistance(this Vec3d v1, Vec3d v2) 19 | { 20 | return (v1.X - v2.X).Square() + (v1.Y - v2.Y).Square() + (v1.Z - v2.Z).Square(); 21 | } 22 | 23 | /// 24 | /// Replace X value 25 | /// 26 | /// 27 | /// 28 | /// 29 | public static Vec3d SetX(this Vec3d v3, decimal x) 30 | { 31 | return new Vec3d(x, v3.Y, v3.Z); 32 | } 33 | 34 | /// 35 | /// Replace Y value 36 | /// 37 | /// 38 | /// 39 | /// 40 | public static Vec3d SetY(this Vec3d v3, decimal y) 41 | { 42 | return new Vec3d(v3.X, y, v3.Z); 43 | } 44 | 45 | /// 46 | /// Replace Z value 47 | /// 48 | /// 49 | /// 50 | /// 51 | public static Vec3d SetZ(this Vec3d v3, decimal z) 52 | { 53 | return new Vec3d(v3.X, v3.Y, z); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /H3Lib/Readme.md: -------------------------------------------------------------------------------- 1 | # H3Lib Data Structures 2 | 3 | The majority of these items are readonly struct value types. 4 | This provides the ability for a fluid architecture, and a 5 | simpler approach to the API rather than having a large amount 6 | of ref based parameters in function calls. It also allows 7 | for removing some overhead on detecting duplicates, as there's 8 | some internal functionality that uses HashSet for dealing 9 | with what could be overlapping data during recursions. 10 | 11 | A quick example going from the original C to the 3.1.1 based C# 12 | version to the current method follows: 13 | 14 | **Original C** 15 | ```c++ 16 | _ijkScale(&transVec, unitScaleByCIIres[adjRes] * 3); 17 | _ijkAdd(ijk, &transVec, ijk); 18 | _ijkNormalize(ijk); 19 | ``` 20 | **C# v3.1.1** 21 | ```c# 22 | CoordIJK transVec = fijkOrient.translate; 23 | CoordIJK._ijkScale(ref transVec, unitScaleByCIIres[adjRes] * 3); 24 | CoordIJK._ijkAdd(ijk, transVec, ref ijk); 25 | CoordIJK_ijkNormalize(ref ijk); 26 | ``` 27 | **C# v.3.7.1** 28 | ```c# 29 | ijk = 30 | (ijk + fijkOrient.Translate * Constants.FaceIjk.UnitScaleByCiiRes[adjRes] * 3) 31 | .Normalized(); 32 | ``` 33 | There's tradeoffs, such as you can no longer do something like: 34 | ```C# 35 | H3Index h3 = new H3Index(); 36 | h3.SetResolution(12); 37 | ``` 38 | You now have to reassign the result of the action like so: 39 | ```C# 40 | H3Index h3 = new H3Index(); 41 | h3 = h3.SetResolution(12); 42 | ``` 43 | But it _does_ let you do something like so: 44 | ```C# 45 | h3Index h3 = new h3Index().SetResolution(12); 46 | ``` 47 | Again, like I said, tradeoffs. I'm fine with it even if it led to many hours finding all the 48 | assignment replacements in the code. 49 | 50 | The exception to this design are the linked geo data structures. Those are regular 51 | classes with references to each other, though I've added the .Net LinkedList to handle 52 | the work for maintaining loops and such. More on those, later. 53 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3CellArea.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using H3Lib; 4 | using H3Lib.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace TestSuite 8 | { 9 | [TestFixture] 10 | public class TestH3CellArea 11 | { 12 | private static readonly decimal[] AreasKm2 = 13 | { 14 | 2.562182162955496e+06m, 4.476842018179411e+05m, 6.596162242711056e+04m, 15 | 9.228872919002590e+03m, 1.318694490797110e+03m, 1.879593512281298e+02m, 16 | 2.687164354763186e+01m, 3.840848847060638e+00m, 5.486939641329893e-01m, 17 | 7.838600808637444e-02m, 1.119834221989390e-02m, 1.599777169186614e-03m, 18 | 2.285390931423380e-04m, 3.264850232091780e-05m, 4.664070326136774e-06m, 19 | 6.662957615868888e-07m 20 | }; 21 | 22 | [Test] 23 | public void SpecificCellArea() 24 | { 25 | var gc = new GeoCoord(0, 0); 26 | 27 | var testAreas = new List(); 28 | var diffs = new List(); 29 | 30 | for (int res = 0; res <= Constants.H3.MAX_H3_RES - 1; res++) 31 | { 32 | H3Index cell = gc.ToH3Index(res); 33 | decimal area = cell.CellAreaKm2(); 34 | testAreas.Add(area); 35 | diffs.Add(Math.Abs(area-AreasKm2[res])); 36 | } 37 | 38 | for (int i = 0; i <= Constants.H3.MAX_H3_RES - 1; i++) 39 | { 40 | // TODO: Figure out why res 1 is 1e-5 difference error while others are <= 1e-9 41 | // TODO: Fix it later. Spent a day on it, and it's close enough for government work at this point. 42 | Assert.IsTrue( 43 | Math.Abs(testAreas[i] - AreasKm2[i]) < 1e-4m, 44 | $"Failure on index {i}" 45 | ); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api-Indexing.md: -------------------------------------------------------------------------------- 1 | # Indexing Functions 2 | 3 | These functions are used for finding the H3 index containing coordinates, 4 | and for finding the center and boundary of H3 indexes. 5 | 6 | ## GeoToH3 7 | 8 | ```c# 9 | H3Index Api.GeoToH3(GeoCoord g, int r) 10 | ``` 11 | 12 | ### GeoToH3 Summary 13 | 14 | Find the H3 index of the resolution res cell containing the lat/lng 15 | 16 | ### GeoToH3 Parameters 17 | 18 | | Name | Type | Description | 19 | |------|------|-------------| 20 | | g | H3Lib.GeoCoord | Geographical coordinate (in radians) data type | 21 | | r | int | Resolution (0-15 inclusive) of resulting H3Index | 22 | 23 | ## H3ToGeo 24 | 25 | ```c# 26 | void Api.H3ToGeo(H3Index h3, out GeoCoord g) 27 | ``` 28 | 29 | ### H3ToGeo Summary 30 | 31 | Find the lat/lon center point g of the cell h3 32 | 33 | _Note:_ This is provided to map the same general syntax 34 | of the C library. You will probably want to use the ToGeo 35 | method for H3Index data types. 36 | 37 | ### H3ToGeo Parameters 38 | 39 | | Name | Type | Description | 40 | |------|------|-------------| 41 | | h3 | H3Lib.H3Index | The H3Index cell to find the centroid of | 42 | | g | **out** H3Lib.GeoCoord | The geographical coordinate that is the center of the cell | 43 | 44 | ## H3ToGeoBoundary 45 | 46 | ```c# 47 | void Api.H3ToGeoBoundary(H3Index h3, out GeoBoundary gb) 48 | ``` 49 | 50 | ### H3ToGeoBoundary Summary 51 | 52 | Gives the cell boundary in lat/lon coordinates for the cell h3 53 | 54 | _Note:_ Similar to H3ToGeo in regards to C API mapping, and an 55 | assigned variable. The preferred method would be ToGeoBoundary(). 56 | 57 | ### H3ToGeoBoundary Parameters 58 | 59 | | Name | Tyoe | Description | 60 | |------|------|-------------| 61 | | h3 | H3Lib.H3Index | The H3Index to find the GeoBoundary of | 62 | | g | **out** H3Lib.GeoBoundary | The GeoBoundary defining the vertices of the H3Index cell | 63 | 64 |
65 | 66 | [Return to Uber API Table of Contents](Uber-Api.md) 67 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToLocalIj/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandLineParser.Arguments; 4 | using H3Lib; 5 | using H3Lib.Extensions; 6 | 7 | namespace H3ToLocalIj 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | using var parser = new CommandLineParser.CommandLineParser(); 14 | 15 | args = args.Select(s => s.ToLower()).ToArray(); 16 | 17 | try 18 | { 19 | var argParser = new H3ToLocalIjArguments(); 20 | parser.ExtractArgumentAttributes(argParser); 21 | parser.ParseCommandLine(args); 22 | ProcessArguments(argParser); 23 | } 24 | catch (Exception) 25 | { 26 | Console.WriteLine("Unable to parse input."); 27 | parser.ShowUsage(); 28 | } 29 | } 30 | 31 | private static void ProcessArguments(H3ToLocalIjArguments argParser) 32 | { 33 | var origin = new H3Index(argParser.OriginH3); 34 | var index = new H3Index(argParser.IndexH3); 35 | 36 | if (!origin.IsValid()) 37 | { 38 | Console.WriteLine("Origin is invalid."); 39 | return; 40 | } 41 | 42 | (int status, var result) = origin.ToLocalIjExperimental(index); 43 | 44 | Console.WriteLine 45 | ( 46 | status != 0 47 | ? "NA" 48 | : $"{result.I} {result.J}" 49 | ); 50 | } 51 | } 52 | 53 | 54 | 55 | public class H3ToLocalIjArguments 56 | { 57 | [BoundedValueArgument(typeof(ulong), 'o', "origin", Optional = false, Description = "Origin H3Index")] 58 | public ulong OriginH3; 59 | 60 | [BoundedValueArgument(typeof(ulong), 'i', "index", Optional = false, Description = "Index H3Index")] 61 | public ulong IndexH3; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /H3Lib/Extensions/Readme.md: -------------------------------------------------------------------------------- 1 | # Extension methods 2 | 3 | In the previous C# version, to provide the same functionality 4 | the C code did, I used a lot of ref parameters in function 5 | calls. This led to a messy code base, especially since I'd 6 | have to copy a property from a class, perform an operation on 7 | that property, then reassign the original class with it. 8 | 9 | There's also sections in the original C code that use fairly 10 | standard hashing functions with buckets and linked lists, and 11 | C# has a Hashset<T> that works just fine so long as you 12 | have good immutable data items. 13 | 14 | That led to a majority of the data structures from the original 15 | C code to being turned into C# readonly struct types. Which 16 | in turn can create other issues. In the end, I decided it was 17 | a viable approach. 18 | 19 | As such, to perform operations that mutate values, you have to 20 | reassign the value from the result of an operation. 21 | 22 | So, ```h.SetResolution(10)``` becomes ```h = h.SetResolution(10)``` 23 | 24 | Dealing with individual fields/properties inside a struct becomes 25 | a little more lengthy, but I think the payoff is worth it. 26 | 27 | With operations becoming more external, along with the 28 | reassignments needed, I started leaning towards making the 29 | functions external as static extension methods. 30 | 31 | In addition, it made a bit more sense to me since I could 32 | segregate them into the domain of which struct they worked on. 33 | 34 | While it makes sense in the C code to provide functions that 35 | work on multiple data types due to having a requirement to 36 | access data provided by the corresponding ```.h``` file during 37 | compilation, it becomes a little noisy to me. Additionally, 38 | switching from procedural to OOP is going to require some 39 | structural changes. 40 | 41 | Ergo, operations on data structures are contained in these 42 | extension method classes. Some will get integrated back to 43 | their data structures at a later iteration such as 3.7.1.# 44 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestLinkedGeo.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestLinkedGeo 9 | { 10 | private static readonly GeoCoord Vertex1 = 11 | new GeoCoord().SetDegrees(87.372002166m, 166.160981117m); 12 | private static readonly GeoCoord Vertex2 = 13 | new GeoCoord().SetDegrees(87.370101364m, 166.160184306m); 14 | private static readonly GeoCoord Vertex3 = 15 | new GeoCoord().SetDegrees(87.369088356m, 166.196239997m); 16 | private static readonly GeoCoord Vertex4 = 17 | new GeoCoord().SetDegrees(87.369975080m, 166.233115768m); 18 | 19 | [Test] 20 | public void CreateLinkedGeo() 21 | { 22 | var polygon = new LinkedGeoPolygon(); 23 | 24 | var loop = polygon.AddNewLinkedLoop(); 25 | Assert.IsNotNull(loop); 26 | var coord = loop.AddLinkedCoord(Vertex1); 27 | Assert.IsNotNull(coord); 28 | coord = loop.AddLinkedCoord(Vertex2); 29 | Assert.IsNotNull(coord); 30 | coord = loop.AddLinkedCoord(Vertex3); 31 | Assert.IsNotNull(coord); 32 | 33 | loop = polygon.AddNewLinkedLoop(); 34 | Assert.IsNotNull(loop); 35 | coord = loop.AddLinkedCoord(Vertex2); 36 | Assert.IsNotNull(coord); 37 | coord = loop.AddLinkedCoord(Vertex4); 38 | Assert.IsNotNull(coord); 39 | 40 | Assert.AreEqual(1,polygon.CountPolygons); 41 | Assert.AreEqual(2,polygon.CountLoops); 42 | Assert.AreEqual(3,polygon.First.Count); 43 | Assert.AreEqual(2,polygon.Last.Count); 44 | 45 | var nextPolygon = polygon.AddNewLinkedGeoPolygon(); 46 | Assert.IsNotNull(nextPolygon); 47 | Assert.AreEqual(2,polygon.CountPolygons); 48 | 49 | polygon.Clear(); 50 | } 51 | 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Apps/Filters/HexRange/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandLineParser.Arguments; 4 | using H3Lib; 5 | using H3Lib.Extensions; 6 | 7 | namespace HexRange 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | using var parser = new CommandLineParser.CommandLineParser(); 14 | 15 | args = args.Select(s => s.ToLower()).ToArray(); 16 | 17 | try 18 | { 19 | var argParser = new HexRangeArguments(); 20 | parser.ExtractArgumentAttributes(argParser); 21 | parser.ParseCommandLine(args); 22 | ProcessArguments(argParser); 23 | } 24 | catch (Exception) 25 | { 26 | Console.WriteLine("Unable to parse input."); 27 | parser.ShowUsage(); 28 | } 29 | } 30 | 31 | private static void ProcessArguments(HexRangeArguments argParser) 32 | { 33 | var radius = argParser.Kradius; 34 | var origin = new H3Index(argParser.OriginH3); 35 | 36 | if (!origin.IsValid()) 37 | { 38 | Console.WriteLine("Origin is invalid."); 39 | return; 40 | } 41 | 42 | (int status, var values) = origin.HexRange(radius); 43 | 44 | if (status != 0) 45 | { 46 | Console.WriteLine("0"); 47 | return; 48 | } 49 | 50 | foreach (var value in values) 51 | { 52 | Console.WriteLine(value.ToString()); 53 | } 54 | } 55 | } 56 | 57 | public class HexRangeArguments 58 | { 59 | [BoundedValueArgument(typeof(int), 'k', Optional = false, Description = "k radius")] 60 | public int Kradius; 61 | 62 | [BoundedValueArgument(typeof(ulong), 'o', "origin", Optional = false, Description = "Origin H3Index")] 63 | public ulong OriginH3; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3DistanceExhaustive.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | using TestSuite.Lib; 5 | 6 | namespace TestSuite 7 | { 8 | [TestFixture] 9 | public class TestH3DistanceExhaustive 10 | { 11 | private static readonly int[] MAX_DISTANCES = {1, 2, 5, 12, 19, 26}; 12 | 13 | private static void h3Distance_identity_assertions(H3Index h3) 14 | { 15 | Assert.AreEqual(0, h3.DistanceTo(h3)); 16 | } 17 | 18 | private static void h3Distance_kRing_assertions(H3Index h3) 19 | { 20 | int r = h3.Resolution; 21 | Assert.LessOrEqual(r, 5); 22 | int maxK = MAX_DISTANCES[r]; 23 | 24 | var lookup = h3.KRingDistances(maxK); 25 | 26 | foreach ((H3Index cell, int distance) in lookup) 27 | { 28 | if (lookup[cell] == 0) 29 | { 30 | continue; 31 | } 32 | 33 | int calcDistance = h3.DistanceTo(cell); 34 | Assert.IsTrue(calcDistance == distance || calcDistance == -1); 35 | } 36 | } 37 | 38 | [Test] 39 | public void h3Distance_identity() 40 | { 41 | Utility.IterateAllIndexesAtRes(0, h3Distance_identity_assertions); 42 | Utility.IterateAllIndexesAtRes(1, h3Distance_identity_assertions); 43 | Utility.IterateAllIndexesAtRes(2, h3Distance_identity_assertions); 44 | } 45 | 46 | [Test] 47 | public void h3Distance_kRing() 48 | { 49 | Utility.IterateAllIndexesAtRes(0, h3Distance_kRing_assertions); 50 | Utility.IterateAllIndexesAtRes(1, h3Distance_kRing_assertions); 51 | Utility.IterateAllIndexesAtRes(2, h3Distance_kRing_assertions); 52 | // Don't iterate all of res 3, to save time 53 | Utility.IterateAllIndexesAtResPartial(3, h3Distance_kRing_assertions, 27); 54 | // Further resolutions aren't tested to save time. 55 | } 56 | 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestVertex.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestVertex 9 | { 10 | [Test] 11 | public void VertexNumForDirectionHex() 12 | { 13 | H3Index origin = 0x823d6ffffffffff; 14 | var vertexNums = new int[Constants.H3.NUM_HEX_VERTS]; 15 | 16 | for (var dir = Direction.K_AXES_DIGIT; dir < Direction.NUM_DIGITS; dir++) 17 | { 18 | int vertexNum = origin.VertexNumForDirection(dir); 19 | Assert.IsTrue(vertexNum>=0 && vertexNum < Constants.H3.NUM_HEX_VERTS); 20 | Assert.AreEqual(0, vertexNums[vertexNum]); 21 | vertexNums[vertexNum] = 1; 22 | } 23 | } 24 | 25 | [Test] 26 | public void VertexNumForDirectionPent() 27 | { 28 | H3Index pentagon = 0x823007fffffffff; 29 | var vertexNums = new int[Constants.H3.NUM_PENT_VERTS]; 30 | 31 | for (var dir = Direction .J_AXES_DIGIT; dir < Direction.NUM_DIGITS ; dir++) 32 | { 33 | int vertexNum = pentagon.VertexNumForDirection(dir); 34 | Assert.IsTrue(vertexNum>=0 && vertexNum 7 | /// A single node in a vertex graph, part of a linked list 8 | /// 9 | [DebuggerDisplay("From: {From} => To: {To}")] 10 | public readonly struct VertexNode : IEquatable 11 | { 12 | /// 13 | /// Where the edge starts 14 | /// 15 | public readonly GeoCoord From; 16 | /// 17 | /// Where the edge ends 18 | /// 19 | public readonly GeoCoord To; 20 | 21 | /// 22 | /// Constructor 23 | /// 24 | /// 25 | /// 26 | public VertexNode(GeoCoord toNode, GeoCoord fromNode) 27 | { 28 | To = toNode; 29 | From = fromNode; 30 | } 31 | 32 | /// 33 | /// Equality test 34 | /// 35 | public bool Equals(VertexNode other) 36 | { 37 | return From.Equals(other.From) && To.Equals(other.To); 38 | } 39 | 40 | /// 41 | /// Equality test against unboxed object 42 | /// 43 | public override bool Equals(object obj) 44 | { 45 | return obj is VertexNode other && Equals(other); 46 | } 47 | 48 | /// 49 | /// Hashcode for identity 50 | /// 51 | /// 52 | public override int GetHashCode() 53 | { 54 | return HashCode.Combine(From, To); 55 | } 56 | 57 | /// 58 | /// equality operator 59 | /// 60 | public static bool operator ==(VertexNode left, VertexNode right) 61 | { 62 | return left.Equals(right); 63 | } 64 | 65 | /// 66 | /// inequality operator 67 | /// 68 | public static bool operator !=(VertexNode left, VertexNode right) 69 | { 70 | return !left.Equals(right); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api.md: -------------------------------------------------------------------------------- 1 | # Uber H3 API 2 | 3 | This is the direct translation of the Uber H3 API, with some changes, 4 | since it's C# instead of C. No pointers or pointer addresses, so 5 | functions that use them in the original tend to have an **out** parameter 6 | where the results will be placed. 7 | 8 | Additionally, something I discovered is that Microsoft basically took a 9 | shortcut in their trigonometric functions that are still accurate to many 10 | decimal points, but not enough. As such, this required a retool to use 11 | decimal data types and an external library to maintain that accuracy for 12 | trig operations. 13 | 14 | All your non-integer values, as such, will be in the decimal data type. 15 | I'd recommend performing your H3 operations sequentially, then if you have 16 | need of the final result in another type, do your cast then. Interim casts 17 | then recast back to decimal are not guaranteed to maintain correctness. 18 | 19 | ## Contents 20 | [Indexing functions](Uber-Api-Indexing.md) - These functions are used for 21 | finding the H3 index containing coordinates, and for finding the center 22 | and boundary of H3 indexes. 23 | 24 | [Index Inspection Functions](Uber-Api-Index-Inspection.md) - These 25 | functions provide metadata about an H3 index, such as its resolution or 26 | base cell, and provide utilities for converting into and out of the 64-bit 27 | representation of an H3 index. 28 | 29 | [Grid Traversal Functions](Uber-Api-Grid-Traversal.md) - Grid traversal 30 | allows finding cells in the vicinity of an origin cell, and determining 31 | how to traverse the grid from one cell to another. 32 | 33 | [Hierarchal Grid Functions](Uber-Api-Hierarchical-Grid.md) - These 34 | functions permit moving between resolutions in the H3 grid system. The 35 | functions produce parent (coarser) or children (finer) cells. 36 | 37 | [Region Functions](Uber-Api-Region.md) - These functions convert H3 38 | indexes to and from polygonal areas. 39 | 40 | [Unidirectional Edge Functions](Uber-Api-Unidirectional-Edge.md) - 41 | Unidirectional edges allow encoding the directed edge from one cell 42 | to a neighboring cell. 43 | 44 | [Miscellaneous H3 Functions](Uber-Api-Miscellaneous-H3.md) - These 45 | functions include descriptions of the H3 grid system. 46 | -------------------------------------------------------------------------------- /H3Lib/Extensions/DirectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib.Extensions 2 | { 3 | /// 4 | /// Operations for Direction enum type 5 | /// 6 | public static class DirectionExtensions 7 | { 8 | /// 9 | /// Rotates indexing digit 60 degrees counter-clockwise. Returns result. 10 | /// 11 | /// Indexing digit (between 1 and 6 inclusive) 12 | /// 16 | internal static Direction Rotate60CounterClockwise(this Direction digit) 17 | { 18 | switch (digit) 19 | { 20 | case Direction.K_AXES_DIGIT: return Direction.IK_AXES_DIGIT; 21 | case Direction.IK_AXES_DIGIT: return Direction.I_AXES_DIGIT; 22 | case Direction.I_AXES_DIGIT: return Direction.IJ_AXES_DIGIT; 23 | case Direction.IJ_AXES_DIGIT: return Direction.J_AXES_DIGIT; 24 | case Direction.J_AXES_DIGIT: return Direction.JK_AXES_DIGIT; 25 | case Direction.JK_AXES_DIGIT: return Direction.K_AXES_DIGIT; 26 | default: return digit; 27 | }; 28 | } 29 | 30 | /// 31 | /// Rotates indexing digit 60 degrees clockwise. Returns result. 32 | /// 33 | /// Indexing digit (between 1 and 6 inclusive) 34 | /// 38 | internal static Direction Rotate60Clockwise(this Direction digit) 39 | { 40 | switch(digit) 41 | { 42 | case Direction.K_AXES_DIGIT: return Direction.JK_AXES_DIGIT; 43 | case Direction.JK_AXES_DIGIT: return Direction.J_AXES_DIGIT; 44 | case Direction.J_AXES_DIGIT: return Direction.IJ_AXES_DIGIT; 45 | case Direction.IJ_AXES_DIGIT: return Direction.I_AXES_DIGIT; 46 | case Direction.I_AXES_DIGIT: return Direction.IK_AXES_DIGIT; 47 | case Direction.IK_AXES_DIGIT: return Direction.K_AXES_DIGIT; 48 | default: return digit; 49 | }; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Apps/Filters/GeoToH3/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandLineParser.Arguments; 4 | using H3Lib; 5 | using H3Lib.Extensions; 6 | 7 | namespace GeoToH3 8 | { 9 | internal static class Program 10 | { 11 | private static void Main(string[] args) 12 | { 13 | using var parser = new CommandLineParser.CommandLineParser(); 14 | args = args.Select(s => s.ToLower()).ToArray(); 15 | 16 | try 17 | { 18 | var argParser = new GeoToH3Parser(); 19 | parser.ExtractArgumentAttributes(argParser); 20 | parser.ParseCommandLine(args); 21 | ProcessParser(argParser); 22 | } 23 | catch (Exception) 24 | { 25 | Console.WriteLine("Unable to parse input."); 26 | parser.ShowUsage(); 27 | } 28 | } 29 | 30 | private static void ProcessParser(GeoToH3Parser target) 31 | { 32 | 33 | var h3 = new GeoCoord(((decimal)target.Latitude).DegreesToRadians(), ((decimal)target.Longitude).DegreesToRadians()) 34 | .ToH3Index(target.Resolution); 35 | 36 | Console.WriteLine(h3.ToString()); 37 | } 38 | } 39 | 40 | public class GeoToH3Parser 41 | { 42 | [BoundedValueArgument(typeof(double), "latitude", 43 | MinValue = -90.0, MaxValue = 90.0, 44 | Aliases = new []{"lat"}, 45 | Optional = false, 46 | Description = "Latitude in degrees")] 47 | public double Latitude; 48 | 49 | [BoundedValueArgument(typeof(double), "longitude", 50 | MinValue = -180.0, MaxValue = 180.0, 51 | Aliases = new[]{"lon"}, 52 | Optional = false, 53 | Description = "Longitude in degrees")] 54 | public double Longitude; 55 | 56 | [BoundedValueArgument(typeof(int), 'r', "res", 57 | MinValue = 0, MaxValue = 15, 58 | Optional = false, 59 | Description = "Resolution (0-15 inclusive)")] 60 | public int Resolution; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Apps/Filters/KRing/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using CommandLineParser.Arguments; 5 | using H3Lib; 6 | using H3Lib.Extensions; 7 | 8 | namespace KRing 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | using var parser = new CommandLineParser.CommandLineParser(); 15 | 16 | args = args.Select(s => s.ToLower()).ToArray(); 17 | 18 | try 19 | { 20 | var argParser = new HexRangeArguments(); 21 | parser.ExtractArgumentAttributes(argParser); 22 | parser.ParseCommandLine(args); 23 | ProcessArguments(argParser); 24 | } 25 | catch (Exception) 26 | { 27 | Console.WriteLine("Unable to parse input."); 28 | parser.ShowUsage(); 29 | } 30 | } 31 | 32 | private static void ProcessArguments(HexRangeArguments argParser) 33 | { 34 | var radius = argParser.KRadius; 35 | var origin = new H3Index(argParser.Origin); 36 | var showDistance = argParser.Print; 37 | 38 | if (!origin.IsValid()) 39 | { 40 | Console.WriteLine("0"); 41 | return; 42 | } 43 | 44 | var lookup = origin.KRingDistances(radius); 45 | 46 | StringBuilder sb = new StringBuilder(); 47 | foreach (var pair in lookup) 48 | { 49 | sb.Clear(); 50 | sb.Append(pair.Key.ToString()); 51 | if (showDistance) 52 | { 53 | sb.Append($" {pair.Value}"); 54 | } 55 | Console.WriteLine(sb.ToString()); 56 | } 57 | } 58 | } 59 | 60 | public class HexRangeArguments 61 | { 62 | [SwitchArgument("print-distances", false, Optional = true, Description = "Print Distances")] 63 | public bool Print; 64 | 65 | [BoundedValueArgument(typeof(int), 'k', Optional = false, Description = "K Ring radius")] 66 | public int KRadius; 67 | 68 | [BoundedValueArgument(typeof(ulong), 'o', "origin", Optional = false)] 69 | public ulong Origin; 70 | 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /H3Lib/BaseCellRotation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// base cell at a given ijk and required rotations into its system 7 | /// 8 | /// 12 | public readonly struct BaseCellRotation:IEquatable 13 | { 14 | /// 15 | /// base cell number 16 | /// 17 | public readonly int BaseCell; 18 | /// 19 | /// number of ccw 60 degree rotations relative to current face 20 | /// 21 | public readonly int CounterClockwiseRotate60; 22 | 23 | /// 24 | /// constructor 25 | /// 26 | public BaseCellRotation(int baseCell, int counterClockwiseRotate60) 27 | { 28 | BaseCell = baseCell; 29 | CounterClockwiseRotate60 = counterClockwiseRotate60; 30 | } 31 | 32 | /// 33 | /// Test for equality against BaseCellRotation 34 | /// 35 | public bool Equals(BaseCellRotation other) 36 | { 37 | return BaseCell == other.BaseCell && 38 | CounterClockwiseRotate60 == other.CounterClockwiseRotate60; 39 | } 40 | 41 | /// 42 | /// Test for equality against object that can be unboxed to BaseCellRotation 43 | /// 44 | public override bool Equals(object obj) 45 | { 46 | return obj is BaseCellRotation other && Equals(other); 47 | } 48 | 49 | /// 50 | /// Hashcode for identity 51 | /// 52 | public override int GetHashCode() 53 | { 54 | return HashCode.Combine(BaseCell, CounterClockwiseRotate60); 55 | } 56 | 57 | /// 58 | /// Test for equality 59 | /// 60 | public static bool operator ==(BaseCellRotation left, BaseCellRotation right) 61 | { 62 | return left.Equals(right); 63 | } 64 | 65 | /// 66 | /// Test for inequality 67 | /// 68 | public static bool operator !=(BaseCellRotation left, BaseCellRotation right) 69 | { 70 | return !left.Equals(right); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestHexRanges.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using H3Lib; 4 | using H3Lib.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace TestSuite 8 | { 9 | [TestFixture] 10 | public class TestHexRanges 11 | { 12 | private static GeoCoord sf = new GeoCoord(0.659966917655m, 2 * 3.14159m - 2.1364398519396m); 13 | private static H3Index sfHex = sf.ToH3Index(9); 14 | 15 | private static List k1 = new List 16 | { 17 | 0x89283080ddbffff, 0x89283080c37ffff, 0x89283080c27ffff, 18 | 0x89283080d53ffff, 0x89283080dcfffff, 0x89283080dc3ffff 19 | }; 20 | 21 | private static List withPentagon = new List 22 | { 23 | 0x8029fffffffffff, 24 | 0x801dfffffffffff 25 | }; 26 | 27 | 28 | [Test] 29 | public void IdentityKRing() 30 | { 31 | var (status, result) = sfHex.HexRange(0); 32 | 33 | Assert.AreEqual(0, status); 34 | Assert.AreEqual(sfHex, result[0]); 35 | } 36 | 37 | [Test] 38 | public void Ring1of1() 39 | { 40 | (int status, var result) = k1.HexRanges(1); 41 | 42 | Assert.AreEqual(0, status); 43 | Assert.IsTrue(result.All(r => r != 0)); 44 | 45 | foreach (var index in k1) 46 | { 47 | Assert.IsTrue(result.Contains(index)); 48 | } 49 | } 50 | 51 | [Test] 52 | public void Ring2of1() 53 | { 54 | (int status, var result) = k1.HexRanges(2); 55 | 56 | Assert.AreEqual(0, status); 57 | Assert.IsTrue(result.All(r => r != 0)); 58 | 59 | foreach (var index in k1) 60 | { 61 | Assert.IsTrue(result.Contains(index)); 62 | } 63 | } 64 | 65 | [Test] 66 | public void Failed() 67 | { 68 | (int status, var result) = withPentagon.HexRanges(1); 69 | 70 | Assert.AreNotEqual(0, status); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /H3Lib/Vec3d.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace H3Lib 5 | { 6 | /// 7 | /// 3D floating point structure 8 | /// 9 | public readonly struct Vec3d:IEquatable 10 | { 11 | /// 12 | /// X Coordinate 13 | /// 14 | public readonly decimal X; 15 | /// 16 | /// Y Coordinate 17 | /// 18 | public readonly decimal Y; 19 | /// 20 | /// Z Coordinate 21 | /// 22 | public readonly decimal Z; 23 | 24 | /// 25 | /// Constructor 26 | /// 27 | public Vec3d(decimal x, decimal y, decimal z) 28 | { 29 | X = x; 30 | Y = y; 31 | Z = z; 32 | } 33 | 34 | /// 35 | /// Debug info in string format 36 | /// 37 | public override string ToString() 38 | { 39 | return $"Vec3d (X,Y,Z) {X:F6}, {Y:F6}, {Z:F6}"; 40 | } 41 | 42 | /// 43 | /// Equality test 44 | /// 45 | public bool Equals(Vec3d other) 46 | { 47 | return X.Equals(other.X) && 48 | Y.Equals(other.Y) && 49 | Z.Equals(other.Z); 50 | } 51 | 52 | /// 53 | /// Equality test 54 | /// 55 | public override bool Equals(object obj) 56 | { 57 | return obj is Vec3d other && 58 | Equals(other); 59 | } 60 | 61 | /// 62 | /// Hashcode for identity 63 | /// 64 | /// 65 | public override int GetHashCode() 66 | { 67 | return HashCode.Combine(X, Y, Z); 68 | } 69 | 70 | /// 71 | /// Equality operator 72 | /// 73 | /// 74 | /// 75 | /// 76 | public static bool operator ==(Vec3d left, Vec3d right) 77 | { 78 | return left.Equals(right); 79 | } 80 | 81 | /// 82 | /// inequality operator 83 | /// 84 | public static bool operator !=(Vec3d left, Vec3d right) 85 | { 86 | return !left.Equals(right); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /H3Lib/FaceIjk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace H3Lib 5 | { 6 | /// 7 | /// Functions for working with icosahedral face-centered hex IJK 8 | /// coordinate systems. 9 | /// 10 | [DebuggerDisplay("Face: {Face} Coord: {Coord}")] 11 | public readonly struct FaceIjk:IEquatable 12 | { 13 | /// 14 | /// face number 15 | /// 16 | public readonly int Face; 17 | 18 | /// 19 | /// ijk coordinates on that face 20 | /// 21 | public readonly CoordIjk Coord; 22 | 23 | /// 24 | /// constructor 25 | /// 26 | public FaceIjk(int f, CoordIjk cijk) 27 | { 28 | Face = f; 29 | Coord = cijk; 30 | } 31 | 32 | /// 33 | /// constructor 34 | /// 35 | public FaceIjk(FaceIjk fijk) 36 | { 37 | Face = fijk.Face; 38 | Coord = fijk.Coord; 39 | } 40 | 41 | /// 42 | /// Debug data in string 43 | /// 44 | public override string ToString() 45 | { 46 | return $"FaceIjk: Face: {Face} Coord: {Coord}"; 47 | } 48 | 49 | /// 50 | /// Equality test 51 | /// 52 | public bool Equals(FaceIjk other) 53 | { 54 | return Face == other.Face && Coord.Equals(other.Coord); 55 | } 56 | 57 | /// 58 | /// Equality test on unboxed object 59 | /// 60 | public override bool Equals(object obj) 61 | { 62 | return obj is FaceIjk other && Equals(other); 63 | } 64 | 65 | /// 66 | /// Hashcode for identity 67 | /// 68 | public override int GetHashCode() 69 | { 70 | return HashCode.Combine(Face, Coord); 71 | } 72 | 73 | /// 74 | /// Equality operator 75 | /// 76 | public static bool operator ==(FaceIjk left, FaceIjk right) 77 | { 78 | return left.Equals(right); 79 | } 80 | 81 | /// 82 | /// Inequality operator 83 | /// 84 | public static bool operator !=(FaceIjk left, FaceIjk right) 85 | { 86 | return !left.Equals(right); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ v3.7.1 ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ v3.7.1 ] 20 | schedule: 21 | - cron: '25 11 * * 3' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | language: [ 'csharp' ] 32 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 33 | # Learn more: 34 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v2 39 | 40 | # Initializes the CodeQL tools for scanning. 41 | - name: Initialize CodeQL 42 | uses: github/codeql-action/init@v1 43 | with: 44 | languages: ${{ matrix.language }} 45 | # If you wish to specify custom queries, you can do so here or in a config file. 46 | # By default, queries listed here will override any specified in a config file. 47 | # Prefix the list here with "+" to use these queries and those in the config file. 48 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 49 | 50 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 51 | # If this step fails, then you should remove it and run the build manually (see below) 52 | - name: Autobuild 53 | uses: github/codeql-action/autobuild@v1 54 | 55 | # ℹ️ Command-line programs to run using the OS shell. 56 | # 📚 https://git.io/JvXDl 57 | 58 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 59 | # and modify them (or add more) to build your code if your project 60 | # uses a compiled language 61 | 62 | #- run: | 63 | # make bootstrap 64 | # make release 65 | 66 | - name: Perform CodeQL Analysis 67 | uses: github/codeql-action/analyze@v1 68 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3SetToVertexGraph.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | using NUnit.Framework; 5 | 6 | namespace TestSuite 7 | { 8 | [TestFixture] 9 | public class TestH3SetToVertexGraph 10 | { 11 | [Test] 12 | public void Empty() 13 | { 14 | var graph = new VertexGraph(); 15 | Assert.AreEqual(0, graph.Count); 16 | graph.Clear(); 17 | 18 | } 19 | 20 | [Test] 21 | public void SingleHex() 22 | { 23 | var set = new List {0x890dab6220bffff}; 24 | VertexGraph graph = set.ToVertexGraph(); 25 | 26 | Assert.AreEqual(6, graph.Size); 27 | graph.Clear(); 28 | } 29 | 30 | [Test] 31 | public void NonContiguous2() 32 | { 33 | var set = new List {0x8928308291bffff, 0x89283082943ffff}; 34 | var graph = set.ToVertexGraph(); 35 | 36 | Assert.AreEqual(12, graph.Size); 37 | graph.Clear(); 38 | } 39 | 40 | [Test] 41 | public void Contiguous2() 42 | { 43 | var set = new List {0x8928308291bffff, 0x89283082957ffff}; 44 | var graph = set.ToVertexGraph(); 45 | 46 | Assert.AreEqual(10, graph.Size); 47 | graph.Clear(); 48 | } 49 | 50 | [Test] 51 | public void Contiguous2distorted() 52 | { 53 | var set = new List {0x894cc5365afffff, 0x894cc536537ffff}; 54 | var graph = set.ToVertexGraph(); 55 | Assert.AreEqual(12, graph.Size); 56 | graph.Clear(); 57 | } 58 | 59 | [Test] 60 | public void Contiguous3() 61 | { 62 | var set = new List 63 | { 64 | 0x8928308288bffff, 0x892830828d7ffff, 65 | 0x8928308289bffff 66 | }; 67 | var graph = set.ToVertexGraph(); 68 | 69 | Assert.AreEqual(12, graph.Size); 70 | graph.Clear(); 71 | } 72 | 73 | [Test] 74 | public void Hole() 75 | { 76 | var set = new List 77 | { 78 | 0x892830828c7ffff, 0x892830828d7ffff, 79 | 0x8928308289bffff, 0x89283082813ffff, 80 | 0x8928308288fffff, 0x89283082883ffff 81 | }; 82 | var graph = set.ToVertexGraph(); 83 | Assert.AreEqual((6 * 3) + 6, graph.Size); 84 | graph.Clear(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /h3net.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | EW 3 | IJ 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | True 18 | True 19 | True 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /H3Lib/FaceOrientIjk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// Information to transform into an adjacent face IJK system 7 | /// 8 | public readonly struct FaceOrientIjk:IEquatable 9 | { 10 | /// 11 | /// face number 12 | /// 13 | public readonly int Face; 14 | 15 | /// 16 | /// res 0 translation relative to primary face 17 | /// 18 | public readonly CoordIjk Translate; 19 | 20 | /// 21 | /// number of 60 degree ccw rotations relative to primary 22 | /// 23 | public readonly int Ccw60Rotations; 24 | 25 | /// 26 | /// Constructor 27 | /// 28 | public FaceOrientIjk(int f, int i, int j, int k, int c) 29 | { 30 | Face = f; 31 | Translate = new CoordIjk(i, j, k); 32 | Ccw60Rotations = c; 33 | } 34 | 35 | /// 36 | /// Constructor 37 | /// 38 | public FaceOrientIjk(int f, CoordIjk translate, int c) 39 | { 40 | Face = f; 41 | Translate = translate; 42 | Ccw60Rotations = c; 43 | } 44 | 45 | /// 46 | /// Equality test 47 | /// 48 | /// 49 | /// 50 | public bool Equals(FaceOrientIjk other) 51 | { 52 | return Face == other.Face && 53 | Translate.Equals(other.Translate) && 54 | Ccw60Rotations == other.Ccw60Rotations; 55 | } 56 | 57 | /// 58 | /// Equality test against unboxed object 59 | /// 60 | public override bool Equals(object obj) 61 | { 62 | return obj is FaceOrientIjk other && Equals(other); 63 | } 64 | 65 | /// 66 | /// Hashcode for identity 67 | /// 68 | /// 69 | public override int GetHashCode() 70 | { 71 | return HashCode.Combine(Face, Translate, Ccw60Rotations); 72 | } 73 | 74 | /// 75 | /// Equality operator 76 | /// 77 | public static bool operator ==(FaceOrientIjk left, FaceOrientIjk right) 78 | { 79 | return left.Equals(right); 80 | } 81 | 82 | /// 83 | /// Inequality operator 84 | /// 85 | public static bool operator !=(FaceOrientIjk left, FaceOrientIjk right) 86 | { 87 | return !left.Equals(right); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /H3Lib/Extensions/CoordIjExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace H3Lib.Extensions 2 | { 3 | /// 4 | /// Extension methods for working with CoordIj type 5 | /// 6 | public static class CoordIjExtensions 7 | { 8 | /// 9 | /// Replace I value 10 | /// 11 | public static CoordIj ReplaceI(this CoordIj ij, int i) 12 | { 13 | return new CoordIj(i, ij.J); 14 | } 15 | 16 | /// 17 | /// replace J value 18 | /// 19 | /// 20 | public static CoordIj ReplaceJ(this CoordIj ij, int j) 21 | { 22 | return new CoordIj(ij.I, j); 23 | } 24 | 25 | /// 26 | /// Transforms coordinates from the IJ coordinate system to the IJK+ coordinate system 27 | /// 28 | /// The input IJ coordinates 29 | /// 30 | /// coordijk.c 31 | /// void ijToIjk 32 | /// 33 | public static CoordIjk ToIjk(this CoordIj ij) 34 | { 35 | return new CoordIjk(ij.I, ij.J, 0).Normalized(); 36 | } 37 | 38 | /// 39 | /// Produces an index for ij coordinates anchored by an origin. 40 | /// 41 | /// The coordinate space used by this function may have deleted 42 | /// regions or warping due to pentagonal distortion. 43 | /// 44 | /// Failure may occur if the index is too far away from the origin 45 | /// or if the index is on the other side of a pentagon. 46 | /// 47 | /// This function is experimental, and its output is not guaranteed 48 | /// to be compatible across different versions of H3. 49 | /// 50 | /// coordinates to index. 51 | /// An anchoring index for the ij coordinate system. 52 | /// 53 | /// Tuple: 54 | /// Item1 indicates status => 0 = Success, other = failure 55 | /// Item2 contains H3Index upon success. 56 | /// 57 | /// 61 | public static (int, H3Index) ToH3Experimental(this CoordIj ij, H3Index origin) 62 | { 63 | // This function is currently experimental. Once ready to be part of the 64 | // non-experimental API, this function (with the experimental prefix) will 65 | // be marked as deprecated and to be removed in the next major version. It 66 | // will be replaced with a non-prefixed function name. 67 | var ijk = ij.ToIjk(); 68 | return ijk.LocalIjkToH3(origin); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /H3Lib/BBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// Geographic bounding box with coordinates defined in radians 7 | /// 8 | public readonly struct BBox:IEquatable 9 | { 10 | /// 11 | /// North limit 12 | /// 13 | public readonly decimal North; 14 | 15 | /// 16 | /// South limit 17 | /// 18 | public readonly decimal South; 19 | 20 | /// 21 | /// East limit 22 | /// 23 | public readonly decimal East; 24 | 25 | /// 26 | /// West limit 27 | /// 28 | public readonly decimal West; 29 | 30 | /// 31 | /// Whether the given bounding box crosses the antimeridian 32 | /// 33 | /// 37 | public bool IsTransmeridian => East < West; 38 | 39 | /// 40 | /// constructor 41 | /// 42 | public BBox(decimal n, decimal s, decimal e, decimal w) 43 | { 44 | North = n; 45 | South = s; 46 | East = e; 47 | West = w; 48 | } 49 | 50 | /// 51 | /// Test for equality within measure of error against other BBox 52 | /// 53 | public bool Equals(BBox other) 54 | { 55 | return 56 | Math.Abs(North - other.North) < Constants.H3.EPSILON_RAD && 57 | Math.Abs(South - other.South) < Constants.H3.EPSILON_RAD && 58 | Math.Abs(East - other.East) < Constants.H3.EPSILON_RAD && 59 | Math.Abs(West - other.West) < Constants.H3.EPSILON_RAD; 60 | } 61 | 62 | /// 63 | /// Test for object that can be unboxed to BBox 64 | /// 65 | public override bool Equals(object obj) 66 | { 67 | return obj is BBox other && Equals(other); 68 | } 69 | 70 | /// 71 | /// Hashcode for identity 72 | /// 73 | /// 74 | public override int GetHashCode() 75 | { 76 | return HashCode.Combine(North, South, East, West); 77 | } 78 | 79 | /// 80 | /// Test for equality 81 | /// 82 | public static bool operator ==(BBox left, BBox right) 83 | { 84 | return left.Equals(right); 85 | } 86 | 87 | /// 88 | /// Test for inequality 89 | /// 90 | public static bool operator !=(BBox left, BBox right) 91 | { 92 | return !left.Equals(right); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /H3Lib/CoordIj.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// IJ Hexagon coordinates. 7 | /// 8 | /// Each axis is spaced 120 degrees apart 9 | /// 10 | public readonly struct CoordIj : IEquatable 11 | { 12 | /// 13 | /// I Component 14 | /// 15 | public readonly int I; 16 | /// 17 | /// J component 18 | /// 19 | public readonly int J; 20 | 21 | /// 22 | /// Constructor 23 | /// 24 | public CoordIj(int i, int j) : this() 25 | { 26 | I = i; 27 | J = j; 28 | } 29 | 30 | /// 31 | /// Constructor 32 | /// 33 | public CoordIj(CoordIj ij) 34 | { 35 | I = ij.I; 36 | J = ij.J; 37 | } 38 | 39 | /// 40 | /// Test for equality 41 | /// 42 | public bool Equals(CoordIj other) 43 | { 44 | return I == other.I && J == other.J; 45 | } 46 | 47 | /// 48 | /// Test for equality on object that can be unboxed to CoordIJ 49 | /// 50 | public override bool Equals(object obj) 51 | { 52 | return obj is CoordIj other && Equals(other); 53 | } 54 | 55 | /// 56 | /// Hashcode for identity 57 | /// 58 | public override int GetHashCode() 59 | { 60 | return HashCode.Combine(I, J); 61 | } 62 | 63 | /// 64 | /// Test for equality 65 | /// 66 | public static bool operator ==(CoordIj left, CoordIj right) 67 | { 68 | return left.Equals(right); 69 | } 70 | 71 | /// 72 | /// Test for inequality 73 | /// 74 | public static bool operator !=(CoordIj left, CoordIj right) 75 | { 76 | return !left.Equals(right); 77 | } 78 | 79 | /// 80 | /// Addition operator 81 | /// 82 | public static CoordIj operator+(CoordIj c1,CoordIj c2) 83 | { 84 | return new CoordIj(c1.I + c2.I, c1.J + c2.J); 85 | } 86 | 87 | /// 88 | /// Subtraction operator 89 | /// 90 | public static CoordIj operator-(CoordIj c1,CoordIj c2) 91 | { 92 | return new CoordIj(c1.I - c2.I, c1.J - c2.J); 93 | } 94 | 95 | /// 96 | /// Multiply operator for scaling 97 | /// 98 | public static CoordIj operator *(CoordIj c, int scalar) 99 | { 100 | return new CoordIj(c.I * scalar, c.J * scalar); 101 | } 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api-Region.md: -------------------------------------------------------------------------------- 1 | # Region Functions 2 | 3 | These functions convert H3 indexes to and from polygonal areas. 4 | 5 | ## Polyfill 6 | 7 | ```c# 8 | void aPI.PolyFill(GeoPolygon polygon, int r, out List outCells) 9 | ``` 10 | 11 | ### Polyfill Summary 12 | 13 | polyfill takes a given GeoJSON-like data and fills it with the hexagons that are contained by the GeoJSON-like data structure. 14 | 15 | The current implementation is very primitive and slow, but correct, performing a point-in-poly operation on every hexagon in a k-ring defined around the given geofence. 16 | 17 | ### Polyfill Parameters 18 | 19 | | Name | Type | Description | 20 | |------|------|-------------| 21 | |polygon|H3Lib.GeoPolygon|Polygon to be filled| 22 | |r|int|Resolution of cells to fill polygon| 23 | |outCells|**out** List<H3Lib.H3Index>|Cells that fill the polygon| 24 | 25 | ## MaxPolyfillSize 26 | 27 | ```c# 28 | int Api.MaxPolyFillSize(GeoPolygon polygon, int r) 29 | ``` 30 | 31 | ### MaxPolyfillSize Summary 32 | 33 | maxPolyfillSize returns the number of hexagons to allocate space for when performing a polyfill on the given GeoJSON-like data structure. 34 | 35 | ### MaxPolyfillSize Parameters 36 | 37 | | Name | Type | Description | 38 | |------|------|-------------| 39 | |polygon|H3Lib.GeoPolygon|Polygon to be filled| 40 | |r|int|Resolution of cells to fill polygon| 41 | 42 | ## H3SetToLinkedGeo 43 | 44 | ```c# 45 | void Api.H3SetToLinkedGeo(List h3Set, out LinkedGeoPolygon outPolygon) 46 | ``` 47 | 48 | ### H3SetToLinkedGeo Summary 49 | 50 | Create a LinkedGeoPolygon describing the outline(s) of a set of hexagons. Polygon outlines will follow GeoJSON MultiPolygon order: 51 | 52 | Each polygon will have one outer loop, which is first in the list, followed by any holes. 53 | 54 | It is the responsibility of the caller to call DestroyLinkedPolygon on the populated linked geo structure, or the memory for that structure will not be freed. 55 | 56 | It is expected 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 output. 57 | 58 | ### H3SetToLinkedGeo Parameters 59 | 60 | | Name | Type | Description | 61 | |------|------|-------------| 62 | |h3Set|List<H3Lib.H3Index>|H3Index cells to create a polygon from| 63 | |outPolygon|**out** H3Lib.LinkedGeoPolygon|The resulting polygon| 64 | 65 | ## DestroyLinkedPolygon 66 | 67 | ```c# 68 | void Api.DestroyLinkedPolygon(LinkedGeoPolygon polygon) 69 | ``` 70 | 71 | ### DestroyLinkedPolygon Summary 72 | 73 | Free all allocated memory for a linked geo structure. 74 | The caller is responsible for freeing memory allocated 75 | to the input polygon struct. 76 | 77 | ### DestroyLinkedPolygon Parameters 78 | 79 | | Name | Type | Description | 80 | |------|------|-------------| 81 | |polygon|LinkedGeoPolygon|Polygon to clear| 82 | 83 |
84 | 85 | [Return to Uber API Table of Contents](Uber-Api.md) 86 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3LineExhaustive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using H3Lib; 5 | using H3Lib.Extensions; 6 | using Microsoft.VisualBasic; 7 | using NUnit.Framework; 8 | using TestSuite.Lib; 9 | 10 | namespace TestSuite 11 | { 12 | [TestFixture] 13 | public class TestH3LineExhaustive 14 | { 15 | private static readonly int[] MAX_DISTANCES = {1, 2, 5, 12, 19, 26}; 16 | 17 | /** 18 | * Property-based testing of h3Line output 19 | */ 20 | private static void h3Line_assertions(H3Index start, H3Index end) 21 | { 22 | (int err, IEnumerable line) = start.LineTo(end); 23 | 24 | var check = line.ToList(); 25 | Assert.AreEqual(0, err); 26 | Assert.AreEqual(start, check.First()); 27 | Assert.AreEqual(end, check.Last()); 28 | 29 | for (int i = 1; i < check.Count; i++) 30 | { 31 | Assert.IsTrue(check[i].IsValid()); 32 | Assert.IsTrue(check[i].IsNeighborTo(check[i - 1])); 33 | if (i > 1) 34 | { 35 | Assert.IsFalse(check[i].IsNeighborTo(check[i - 2])); 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * Tests for invalid h3Line input 42 | */ 43 | private static void h3Line_invalid_assertions(H3Index start, H3Index end) 44 | { 45 | (int err, _) = start.LineTo(end); 46 | Assert.AreNotEqual(0, err); 47 | } 48 | 49 | /** 50 | * Test for lines from an index to all neighbors within a kRing 51 | */ 52 | private static void h3Line_kRing_assertions(H3Index h3) 53 | { 54 | int r = h3.Resolution; 55 | Assert.LessOrEqual(r, 5); 56 | int maxK = MAX_DISTANCES[r]; 57 | 58 | if (h3.IsPentagon()) 59 | { 60 | return; 61 | } 62 | 63 | var neighbors = h3.KRing(maxK); 64 | 65 | for (int i = 0; i < neighbors.Count; i++) 66 | { 67 | if (neighbors[i] == 0) 68 | { 69 | continue; 70 | } 71 | 72 | int distance = h3.DistanceTo(neighbors[i]); 73 | if (distance >= 0) 74 | { 75 | h3Line_assertions(h3, neighbors[i]); 76 | } 77 | else 78 | { 79 | h3Line_invalid_assertions(h3, neighbors[i]); 80 | } 81 | } 82 | } 83 | 84 | [Test] 85 | public void h3Line_kRing() 86 | { 87 | Utility.IterateAllIndexesAtRes(0, h3Line_kRing_assertions); 88 | Utility.IterateAllIndexesAtRes(1, h3Line_kRing_assertions); 89 | Utility.IterateAllIndexesAtRes(2, h3Line_kRing_assertions); 90 | // Don't iterate all of res 3, to save time 91 | Utility.IterateAllIndexesAtResPartial(3, h3Line_kRing_assertions, 6); 92 | // Further resolutions aren't tested to save time. 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /OldApi.md: -------------------------------------------------------------------------------- 1 | # Old API 2 | 3 | This is the old API, you might be able to call some of these from Api.cs, 4 | but I wouldn't recommend it. Examine that file for the commands that you 5 | get when you build h3net as a DLL. 6 | 7 | I'd recommend taking some time to go through the documentation you'll find 8 | at [H3Lib/Documentation](H3Lib/Documentation). It might be a bot more obtuse 9 | at this point, but it will be correct. 10 | 11 | ## Try at your own risk 12 | - Api.H3Index GeoToH3(Code.GeoCoord gc, int res) 13 | - Api.GeoCoord H3ToGeo(Code.H3Index h3) 14 | - Api.GeoBoundary H3ToGeoBoundary(Code.H3Index h3) 15 | - int MaxKringSize(int k) 16 | - HexRangeResult HexRange(Code.H3Index origin, int k) 17 | - HexRangeDistancesResult HexRangeDistances(Code.H3Index origin, int k) 18 | - HexRangeResult HexRanges(IEnumerable h3Set, int k) 19 | - HexRangeResult HexRanges(Code.H3Index h3Set, int k) 20 | - H3Index[] Kring(Code.H3Index origin, int k) 21 | - HexRangeDistancesResult KringDistances(Code.H3Index origin, int k) 22 | - HexRangeResult HexRing(Code.H3Index origin, int k) 23 | - int MaxPolyFillSize(Code.GeoPolygon geoPolygon, int res) 24 | - H3Index[] PolyFill(Code.GeoPolygon geoPolygon, int res) 25 | - LinkedGeoPolygon H3SetToLinkedGeo(IEnumerable h3Set) 26 | - void DestroyLinkedPolygon(ref LinkedGeoPolygon polygon) 27 | - double DegreesToRadians(double degrees) 28 | - double RadiansToDegrees(double radians) 29 | - double HexAreaKm2(int res) 30 | - double HexAreaM2(int res) 31 | - double EdgeLengthKm(int res) 32 | - double EdgeLengthM(int res) 33 | - long NumberHexagons(int res) 34 | - int H3GetResolution(Code.H3Index h3) 35 | - int H3GetBaseCell(Code.H3Index h3) 36 | - H3Index StringToH3(string s) 37 | - string H3ToString(Code.H3Index h3) 38 | - int H3IsValid(Code.H3Index h3) 39 | - H3Index H3ToParent(Code.H3Index h3, int parentRes) 40 | - int MaxH3ToChildrenSize(Code.H3Index h3, int childRes) 41 | - H3Index[] H3ToChildren(Code.H3Index h3, int childRes) 42 | - HexRangeResult Compact(List h3Set) 43 | - int MaxUncompactSize(List h3Set, int res) 44 | - HexRangeResult Uncompact(List compactedSet, int res) 45 | - int H3IsResClassIII(Code.H3Index h3) 46 | - bool IsResClassIII(Code.H3Index h3) 47 | - int H3IsPentagon(Code.H3Index h3) 48 | - bool IsPentagon(Code.H3Index h3) 49 | - int H3IndexesAreNeighbors(Code.H3Index origin, Code.H3Index destination) 50 | - bool AreNeighbors(Code.H3Index origin, Code.H3Index destination) 51 | - H3Index GetUniDirectionalEdge(Code.H3Index origin, Code.H3Index destination) 52 | - int H3UniDirectionalEdgeIsValid(Code.H3Index edge) 53 | - bool UniDirectionalEdgeIsValid(Code.H3Index edge) 54 | - H3Index GetOriginH3FromUniDirectionalEdge(Code.H3Index edge) 55 | - H3Index GetDestinationH3FromUniDirectionalEdge(Code.H3Index edge) 56 | - H3Index[] GetH3IndexesFromUnidirectionalEdge(Code.H3Index edge) 57 | - H3Index[] GetH3UniDirectionalEdgesFromHexagon(Code.H3Index origin) 58 | - GeoBoundary GetH3UnidirectionalEdgeBoundary(Code.H3Index edge) 59 | - int H3Distance(Code.H3Index origin, Code.H3Index h3) 60 | - ExperimentalIJ ExperimentalH3ToLocalIj(Code.H3Index origin, Code.H3Index h3) 61 | - HexRangeResult experimentalLocalIjToH3(Code.H3Index origin, Code.LocalIJ.CoordIJ ij) 62 | -------------------------------------------------------------------------------- /Credits.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | I couldn't have done this conversion and update for h3net 3 | as easily without the following: 4 | 5 | * [H3](https://gihub.com/uber/h3) - The actual source code that I've foolishly 6 | converted to C#. I actually use h3net for another project on mobile, which 7 | is why h3net exists, and partly why I haven't updated this in a while. 8 | 9 | My first iteration of _that_ project used generated data sets from 10 | [DGGRID](https://www.discreteglobalgrids.org/) which was not something I 11 | enjoyed since the project uses real time location data, and generating the 12 | hexagons in a 100 km^2 area polygon was at a resolution of ~100 m^2 was an 13 | unpleasant task at the time. 14 | 15 | Not to mention the unit tests! Jumping the gun from 3.1.1 to 3.7.1, along 16 | with an architecture change led to a scary amount of code to examine. 17 | Without having some idea what to expect, I would have probably given up and 18 | limped along with my initial 3.1.1 implementation. 19 | 20 | The folks on the Slack channel provided some good input as well when I 21 | was trying to figure out a thing or two on how data was structured. 22 | 23 | * [H3Explorer](https://h3explorer.com/) - The visual debugger to make sure the 24 | new code worked right or figure out where the unit tests were blowing up. 25 | An excellent way to see that modifying Algos.kRingDistances to an extension 26 | method on H3Index and not converting the supporting functions correctly, as 27 | well, caused it to create a wandering serpent for k values greater than 1 is 28 | fantastic when you can _see_ how it's messing up. 29 | 30 | * [browserling](https://www.browserling.com/tools) - 31 | Having to deal with lists of numbers sometimes in hex, sometimes in decimal, 32 | and all in 64 bit unsigned integers. Copy/paste, click a button, copy and 33 | compare against what you're expecting. I mostly used 34 | [decimal to hex](https://www.browserling.com/tools/dec-to-hex) but there's a 35 | lot of other tools there. 36 | 37 | * The usual suspects, as I need music to code. 38 | * Melissa Etheridge 39 | * Electric Light Orchestra 40 | * Ben Folds 41 | * Billy Joel 42 | * Meat Loaf 43 | * Lin-Manuel Miranda 44 | * Simon and Garfunkel 45 | * Many musicals, including the frequently played: 46 | * Avenue Q 47 | * Beetlejuice 48 | * The Book Of Mormon 49 | * Dear Evan Hansen 50 | * Hadestown 51 | * Hamilton 52 | * Heathers 53 | * In The Heights 54 | * Jersey Boys 55 | * Mean Girls 56 | * Waitress 57 | 58 | * As always, my family for accepting that I'm going to spend a few 59 | hours poking at a keyboard for days on end, and it's not even going 60 | to make cool pictures or be a video game. 61 | 62 | * Mandy Oei, for reminding me that I need to do things before it's too late. 63 | 64 | * **TOOLS!** I took shortcuts for some of the external things. 65 | * For command line parsing, currently using [CommandLineParser](https://github.com/j-maly/CommandLineParser) 66 | * For baseline XML-Doc to Markdown, [Vsxmd](https://github.com/lijunle/Vsxmd) 67 | * Accuracy is important. It helps to know exactly where you're measuring, and 68 | using trigonometry, it's really important you have tour numbers as accurate 69 | as possible. Currently, Microsoft doesn't. So there's 70 | [DecimalMath](https://github.com/nathanpjones/DecimalMath) 71 | * And my favorite unit test runner, [NUnit](https://github.com/nunit/nunit) 72 | -------------------------------------------------------------------------------- /H3Lib/BaseCellData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace H3Lib 4 | { 5 | /// 6 | /// Information on a single base cell 7 | /// 8 | /// 12 | public readonly struct BaseCellData:IEquatable 13 | { 14 | /// 15 | /// "Home" face and normalized ijk coordinates on that face 16 | /// 17 | public readonly FaceIjk HomeFijk; 18 | /// 19 | /// Is this base cell a pentagon? 20 | /// 21 | public readonly int IsPentagon; 22 | /// 23 | /// If it's a pentagon, what are its two clockwise offset faces? 24 | /// 25 | public readonly int[] ClockwiseOffsetPentagon;// [2] 26 | 27 | /// 28 | /// Extended constructor 29 | /// 30 | /// Face of BaseCellData 31 | /// I coordinate 32 | /// J Coordinate 33 | /// K Coordinate 34 | /// Is cell pentagon? 35 | /// offset 1 36 | /// offset 2 37 | public BaseCellData(int face, int faceI, int faceJ, int faceK, int isPentagon, int offset1, int offset2) : this() 38 | { 39 | HomeFijk = new FaceIjk(face, new CoordIjk(faceI, faceJ, faceK)); 40 | IsPentagon = isPentagon; 41 | ClockwiseOffsetPentagon = new[] {offset1, offset2}; 42 | } 43 | 44 | /// 45 | /// Test for equality 46 | /// 47 | /// BaseCellData to test against 48 | public bool Equals(BaseCellData other) 49 | { 50 | return Equals(HomeFijk, other.HomeFijk) && 51 | IsPentagon == other.IsPentagon && 52 | Equals(ClockwiseOffsetPentagon, other.ClockwiseOffsetPentagon); 53 | } 54 | 55 | /// 56 | /// Test for equality against object that can be unboxed 57 | /// 58 | /// Object to unbox if BaseCellData 59 | public override bool Equals(object obj) 60 | { 61 | return obj is BaseCellData other && Equals(other); 62 | } 63 | 64 | /// 65 | /// HashCode for identity 66 | /// 67 | public override int GetHashCode() 68 | { 69 | return HashCode.Combine(HomeFijk, IsPentagon, ClockwiseOffsetPentagon); 70 | } 71 | 72 | /// 73 | /// Test for equality 74 | /// 75 | /// lhs item 76 | /// rhs item 77 | /// 78 | public static bool operator ==(BaseCellData left, BaseCellData right) 79 | { 80 | return left.Equals(right); 81 | } 82 | 83 | /// 84 | /// Test for inequality 85 | /// 86 | /// lhs item 87 | /// rhd item 88 | /// 89 | public static bool operator !=(BaseCellData left, BaseCellData right) 90 | { 91 | return !left.Equals(right); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /H3Lib/LinkedGeoLoop.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using H3Lib.Extensions; 5 | 6 | namespace H3Lib 7 | { 8 | /// 9 | /// A loop node in a linked geo structure, part of a linked list 10 | /// 11 | public class LinkedGeoLoop 12 | { 13 | /// 14 | /// Linked list that stores the vertices 15 | /// 16 | private LinkedList Loop; 17 | 18 | /// 19 | /// Counts how many vetices in this loop 20 | /// 21 | public int Count => Loop.Count; 22 | 23 | /// 24 | /// Presents a copy of the vertices in a linear list 25 | /// 26 | public List Nodes => CopyNodes(); 27 | 28 | /// 29 | /// Gets the first vertex in the list 30 | /// 31 | public LinkedGeoCoord First => GetFirst(); 32 | 33 | /// 34 | /// Indicates if there's any vertices in the loop 35 | /// 36 | public bool IsEmpty => Loop == null || Loop.Count == 0; 37 | 38 | /// 39 | /// Constructor 40 | /// 41 | public LinkedGeoLoop() 42 | { 43 | Loop = new LinkedList(); 44 | } 45 | 46 | /// 47 | /// Makes a copy of the vertices in the loop 48 | /// 49 | /// 50 | private List CopyNodes() 51 | { 52 | if (Loop == null || Loop.Count == 0) 53 | { 54 | return new List(); 55 | } 56 | 57 | var temp = Enumerable 58 | .Range(1, Loop.Count) 59 | .Select(s => new LinkedGeoCoord()) 60 | .ToArray(); 61 | Loop.CopyTo(temp, 0); 62 | return temp.ToList(); 63 | } 64 | 65 | /// 66 | /// Add a new linked coordinate to the current loop 67 | /// 68 | /// Coordinate to add 69 | /// Reference to the coordinate 70 | /// 74 | public LinkedGeoCoord AddLinkedCoord(GeoCoord vertex) 75 | { 76 | var coord = new LinkedGeoCoord(vertex); 77 | Loop.AddLast(coord); 78 | return coord; 79 | } 80 | 81 | /// 82 | /// Clears the list of coords 83 | /// 84 | public void Clear() 85 | { 86 | Loop.Clear(); 87 | } 88 | 89 | /// 90 | /// Clears the list of coords 91 | /// 92 | public void Destroy() 93 | { 94 | Clear(); 95 | } 96 | 97 | /// 98 | /// Returns first vertex or null if there are none. 99 | /// 100 | /// 101 | private LinkedGeoCoord GetFirst() 102 | { 103 | if (Loop == null || Loop.Count < 1) 104 | { 105 | return null; 106 | } 107 | 108 | return Loop.First?.Value; 109 | } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at github.com@callahansplace.org. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api-Hierarchical-Grid.md: -------------------------------------------------------------------------------- 1 | # Hierarchical Grid Functions 2 | 3 | These functions permit moving between resolutions in 4 | the H3 grid system. The functions produce parent 5 | (coarser) or children (finer) cells. 6 | 7 | ## H3ToParent 8 | 9 | ```c# 10 | H3Lib.H3Index Api.H3ToParent(H3Index h, int parentRes) 11 | ``` 12 | 13 | ### H3ToParent Summary 14 | 15 | Returns the parent (coarser) index containing h. 16 | 17 | ### H3ToParent Parameters 18 | 19 | | Name | Type | Description | 20 | |------|------|-------------| 21 | |h|H3Lib.3Index|Cell to find the parent of| 22 | |parentRes|int|coarser resolution| 23 | 24 | ## H3ToChildren 25 | 26 | ```c# 27 | void Api.H3ToChildren(H3Index h, int childRes, out List outChildren) 28 | ``` 29 | 30 | ### H3ToChildren Summary 31 | 32 | Populates children with the indexes contained by h at resolution 33 | childRes. children must be an array of at least size 34 | maxH3ToChildrenSize(h, childRes). 35 | 36 | ### H3ToChildren Parameters 37 | 38 | | Name | Type | Description | 39 | |------|------|-------------| 40 | |h|H3Lib.H3Index|Cell to find children of| 41 | |childRes|int|finer resolution| 42 | |outChildren|**out** List<H3Lib.H3Index>|child cells| 43 | 44 | ## MaxH3ToChildrenSize 45 | 46 | ```c# 47 | long Api.MaxH3ToChildrenSize(H3Index h, int childRes) 48 | ``` 49 | 50 | ### MaxH3ToChildrenSize Summary 51 | 52 | Returns the size of the array needed by h3ToChildren for these inputs. 53 | 54 | ### MaxH3ToChildrenSize Parameters 55 | 56 | | Name | Type | Description | 57 | |------|------|-------------| 58 | |h|H3Lib.H3Index|Cell to find children of| 59 | |childRes|int|Resolution of children| 60 | 61 | ## H3ToCenterChild 62 | 63 | ```c# 64 | H3Lib.H3Index Api.H3ToCenterChild(H3Index h, int childRes) 65 | ``` 66 | 67 | ### H3ToCenterChild Summary 68 | 69 | Returns the center child (finer) index contained by h at resolution childRes. 70 | 71 | ### H3ToCenterChild Parameters 72 | 73 | | Name | Type | Description | 74 | |------|------|-------------| 75 | |h|H3Lib.H3Index|Cell to find center child of| 76 | |childRes|int|finer resolution| 77 | 78 | ## Compact 79 | 80 | ```c# 81 | int Api.Compact(List h3Set, out List outCompacted) 82 | ``` 83 | 84 | ### Compact Summary 85 | 86 | Compacts the set h3Set of indexes as best as possible, into the List 87 | outCompacted. 88 | 89 | Returns 0 on success. 90 | 91 | ### Compact Parameters 92 | 93 | | Name | Type | Description | 94 | |------|------|-------------| 95 | |h3Set|List<H3Lib.H3Index>|Cells to compact| 96 | |outCompacted|**out** List<H3Lib.H3Index>|Compacted cells| 97 | 98 | ## Uncompact 99 | 100 | ```c# 101 | int Api.Uncompact(List compactedSet, out List outCells, int res) 102 | ``` 103 | 104 | ### Uncompact Summary 105 | 106 | Uncompacts the set compactedSet of indexes to the resolution res. 107 | 108 | Returns 0 on success. 109 | 110 | ### Uncompact Parameters 111 | 112 | | Name | Type | Description | 113 | |------|------|-------------| 114 | |compactedSet|List<H3Lib.H3Index>|Compacted cells| 115 | |outCells|**out** List<H3Lib.H3Index>|Uncompacted cells| 116 | |res|int|Resolution to uncompact to| 117 | 118 | ## MaxUncompactSize 119 | 120 | ```c# 121 | long Api.MaxUncompactSize(H3Index compacted, int r) 122 | ``` 123 | 124 | ### MaxUncompactSize Summary 125 | 126 | Returns the size of the array needed by uncompact. 127 | 128 | ### MaxUncompactSize Parameters 129 | 130 | | Name | Type | Description | 131 | |------|------|-------------| 132 | |compacted|H3Lib.H3Index|Cell to uncompact| 133 | |r|int|Resolution to uncompact to| 134 | 135 |
136 | 137 | [Return to Uber API Table of Contents](Uber-Api.md) 138 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/Lib/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using H3Lib; 5 | using H3Lib.Extensions; 6 | using NUnit.Framework; 7 | 8 | namespace TestSuite.Lib 9 | { 10 | /// 11 | /// Miscellaneous useful functions used for unit tests. 12 | /// 13 | public class Utility 14 | { 15 | /// 16 | /// Call the callback for every index at the given resolution. 17 | /// 18 | public static void IterateAllIndexesAtRes(int res, Action callback) 19 | { 20 | IterateAllIndexesAtResPartial(res, callback, Constants.H3.NUM_BASE_CELLS); 21 | } 22 | 23 | /// 24 | /// Call the callback for every index at the given resolution in base 25 | /// cell 0 up to the given base cell number. 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static void IterateAllIndexesAtResPartial(int res, Action callback, int baseCells) 31 | { 32 | Assert.LessOrEqual(baseCells, Constants.H3.NUM_BASE_CELLS); 33 | for (var i = 0; i < baseCells; i++) 34 | { 35 | IterateBaseCellIndexesAtRes(res, callback, i); 36 | } 37 | 38 | } 39 | 40 | /// 41 | /// Call the callback for every index at the given resolution in a 42 | /// specific base cell 43 | /// 44 | public static void IterateBaseCellIndexesAtRes(int res, Action callback, int baseCell) 45 | { 46 | var bc = new H3Index(0, baseCell, 0); 47 | var (_, children) = bc.Uncompact(res); 48 | 49 | foreach (var index in children.Where(c=>c!=0)) 50 | { 51 | callback(index); 52 | } 53 | 54 | children.Clear(); 55 | } 56 | 57 | /// 58 | /// Returns the number of non-invalid indexes in the collection. 59 | /// 60 | public static int CountActualHexagons(List hexagons) 61 | { 62 | return hexagons 63 | .Count(hexagon => hexagon != H3Lib.Constants.H3Index.H3_NULL); 64 | } 65 | 66 | /// 67 | /// List of cells at a given resolutions 68 | /// 69 | /// resolution 70 | private static List GetCellsAtRes(int res) 71 | { 72 | var (_, resCells) = 73 | BaseCellsExtensions.GetRes0Indexes().Uncompact(res); 74 | return resCells.Where(c => c.Value != 0).ToList(); 75 | } 76 | 77 | public static decimal mapSumAllCells_double(int res, Func callback) 78 | { 79 | var cells = GetCellsAtRes(res); 80 | 81 | long N = res.NumHexagons(); 82 | 83 | decimal total = 0.0m; 84 | for (int i = 0; i < N; i++) 85 | { 86 | total += callback(cells[i]); 87 | } 88 | cells.Clear(); 89 | 90 | return total; 91 | } 92 | 93 | public static void iterateAllUnidirectionalEdgesAtRes(int res, Action callback) 94 | { 95 | var cells = GetCellsAtRes(res); 96 | 97 | long n = res.NumHexagons(); 98 | 99 | for (var i = 0; i < n; i++) 100 | { 101 | bool isPentagon = cells[i].IsPentagon(); 102 | var edges = cells[i].GetUniEdgesFromCell(); 103 | 104 | for (var j = 0; j < 6; j++) 105 | { 106 | if (isPentagon && j == 0) 107 | { 108 | continue; 109 | } 110 | 111 | callback(edges[j]); 112 | } 113 | } 114 | } 115 | 116 | 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3Distance.cs: -------------------------------------------------------------------------------- 1 | using H3Lib; 2 | using H3Lib.Extensions; 3 | using NUnit.Framework; 4 | 5 | namespace TestSuite 6 | { 7 | [TestFixture] 8 | public class TestH3Distance 9 | { 10 | // Some indexes that represent base cells. Base cells 11 | // are hexagons except for `pent1`. 12 | private readonly H3Index bc1 = new H3Index(0, 15, 0); 13 | private readonly H3Index bc2 = new H3Index(0, 8, 0); 14 | private readonly H3Index bc3 = new H3Index(0, 31, 0); 15 | private readonly H3Index pent1 = new H3Index(0, 4, 0); 16 | 17 | [Test] 18 | public void TestIndexDistance() 19 | { 20 | var bc = new H3Index(1, 17, 0); 21 | var p = new H3Index(1, 14, 0); 22 | var p2 = new H3Index(1, 14, 2); 23 | var p3 = new H3Index(1, 14, 3); 24 | var p4 = new H3Index(1, 14, 4); 25 | var p5 = new H3Index(1, 14, 5); 26 | var p6 = new H3Index(1, 14, 6); 27 | 28 | Assert.AreEqual(3, bc.DistanceTo(p)); 29 | Assert.AreEqual(2, bc.DistanceTo(p2)); 30 | Assert.AreEqual(3, bc.DistanceTo(p3)); 31 | // TODO works correctly but is rejected due to possible pentagon 32 | // distortion. 33 | // t_assert(H3_EXPORT(h3Distance)(bc, p4) == 3, "distance onto p4"); 34 | // t_assert(H3_EXPORT(h3Distance)(bc, p5) == 4, "distance onto p5"); 35 | Assert.AreEqual(2, bc.DistanceTo(p6)); 36 | } 37 | 38 | [Test] 39 | public void TestIndexDistance2() 40 | { 41 | H3Index origin = 0x820c4ffffffffffL; 42 | // Destination is on the other side of the pentagon 43 | H3Index destination = 0x821ce7fffffffffL; 44 | 45 | // TODO doesn't work because of pentagon distortion. Both should be 5. 46 | Assert.AreEqual(-1, destination.DistanceTo(origin)); 47 | Assert.AreEqual(-1, origin.DistanceTo(destination)); 48 | } 49 | 50 | [Test] 51 | public void H3DistanceBaseCells() 52 | { 53 | Assert.AreEqual(1, bc1.DistanceTo(pent1)); 54 | Assert.AreEqual(1, bc1.DistanceTo(bc2)); 55 | Assert.AreEqual(1, bc1.DistanceTo(bc3)); 56 | Assert.AreEqual(-1,pent1.DistanceTo(bc3)); 57 | } 58 | 59 | [Test] 60 | public void IjkDistance() 61 | { 62 | var z = new CoordIjk(0, 0, 0); 63 | var i = new CoordIjk(1, 0, 0); 64 | var ik = new CoordIjk(1, 0, 1); 65 | var ij = new CoordIjk(1, 1, 0); 66 | var j2 = new CoordIjk(0, 2, 0); 67 | 68 | Assert.AreEqual(0, z.DistanceTo(z)); 69 | Assert.AreEqual(0, i.DistanceTo(i)); 70 | Assert.AreEqual(0, ik.DistanceTo(ik)); 71 | Assert.AreEqual(0, ij.DistanceTo(ij)); 72 | Assert.AreEqual(0, j2.DistanceTo(j2)); 73 | 74 | Assert.AreEqual(1, z.DistanceTo(i)); 75 | Assert.AreEqual(2, z.DistanceTo(j2)); 76 | Assert.AreEqual(1, z.DistanceTo(ik)); 77 | Assert.AreEqual(1, i.DistanceTo(ik)); 78 | Assert.AreEqual(3, ik.DistanceTo(j2)); 79 | Assert.AreEqual(2, ij.DistanceTo(ik)); 80 | } 81 | 82 | [Test] 83 | public void H3DistanceResolutionMismatch() 84 | { 85 | var h1 = new H3Index(0x832830fffffffffL); 86 | var h2 = new H3Index(0x822837fffffffffL); 87 | Assert.AreEqual(-1, h1.DistanceTo(h2)); 88 | } 89 | 90 | [Test] 91 | public void h3DistanceEdge() 92 | { 93 | H3Index origin = 0x832830fffffffffL; 94 | H3Index dest = 0x832834fffffffffL; 95 | var edge = origin.UniDirectionalEdgeTo(dest); 96 | 97 | Assert.AreNotEqual(0, edge.Value); 98 | Assert.AreEqual(0,edge.DistanceTo(origin)); 99 | Assert.AreEqual(0,origin.DistanceTo(edge)); 100 | Assert.AreEqual(1,edge.DistanceTo(dest)); 101 | Assert.AreEqual(1,dest.DistanceTo(edge)); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api-Index-Inspection.md: -------------------------------------------------------------------------------- 1 | # Index Inspection Functions 2 | 3 | These functions provide metadata about an H3 index, such as its 4 | resolution or base cell, and provide utilities for converting 5 | into and out of the 64-bit representation of an H3 index. 6 | 7 | ## H3GetResolution 8 | 9 | ```c# 10 | int Api.H3GetResolution(H3Index h) 11 | ``` 12 | 13 | ### H3GetResolution Summary 14 | 15 | returns the resolution of the provided H3 index 16 | 17 | ### H3GetResolution Parameters 18 | 19 | | Name | Type | Description | 20 | |------|------|-------------| 21 | | h | H3Lib.H3Index | H3Index cell to get resolution of | 22 | 23 | ## H3GetBaseCell 24 | 25 | ```c# 26 | int Api.H3GetBaseCell(H3Index h) 27 | ``` 28 | 29 | ### H3GetBaseCell Summary 30 | 31 | returns the base cell "number" (0 to 121) of the provided H3 cell 32 | 33 | ### H3GetBaseCell Parameters 34 | 35 | | Name | Type | Description | 36 | |------|------|-------------| 37 | | h | H3Lib.H3Index | H3Index cell to find the base cell number of | 38 | 39 | ## StringToH3 40 | 41 | ```c# 42 | H3Index Api.StringToH3(string s) 43 | ``` 44 | 45 | ### StringToH3 Summary 46 | 47 | converts the canonical string format to H3Index format 48 | 49 | Returns 0 on error. 50 | 51 | ### StringToH3 Parameters 52 | 53 | | Name | Type | Description | 54 | |------|------|-------------| 55 | | s | string | string to parse into an H3Index value | 56 | 57 | ## H3ToString 58 | 59 | ```c# 60 | void Api.H3ToString(H3Index h, out string str) 61 | ``` 62 | 63 | ### H3ToString Summary 64 | 65 | Converts the H3Index representation of the 66 | index to the string representation. 67 | 68 | ### H3ToString Parameters 69 | 70 | | Name | Type | Description | 71 | |------|------|-------------| 72 | | h | H3Lib.H3Index | H3Index value to turn into a string | 73 | | str | **out** string | The string representation of the H3Index value | 74 | 75 | ## H3IsValid 76 | 77 | ```c# 78 | int Api.H3IsValid(H3Index h) 79 | ``` 80 | 81 | ### H3IsValid Summary 82 | 83 | Returns non-zero if this is a valid H3 index. 84 | 85 | ### H3IsValid Parameters 86 | 87 | | Name | Type | Description | 88 | |------|------|-------------| 89 | | h | H3Lib.H3Index | H3Index to inspect | 90 | 91 | ## H3IsResClassIII 92 | 93 | ```c# 94 | int Api.H3IsResClassIii(H3Index h) 95 | ``` 96 | 97 | ### H3IsResClassIII Summary 98 | 99 | Returns non-zero if this index has a resolution with 100 | Class III orientation. 101 | 102 | ### H3IsResClassIII Parameters 103 | 104 | | Name | Type | Description | 105 | |------|------|-------------| 106 | | h | H3Lib.H3Index | H3Index cell under examination | 107 | 108 | ## H3IsPentagon 109 | 110 | ```c# 111 | int Api.H3IsPentagon(H3Index h) 112 | ``` 113 | 114 | ### H3IsPentagon Summary 115 | 116 | Returns non-zero if this index represents a pentagonal cell. 117 | 118 | ### H3IsPentagon Parameters 119 | 120 | | Name | Type | Description | 121 | |------|------|-------------| 122 | | h | H3Lib.H3Index | H3Index cell under investigation | 123 | 124 | ## H3GetFaces 125 | 126 | ```c# 127 | void Api.H3GetFaces(H3Index h3, out List outFaces) 128 | ``` 129 | 130 | ### H3GetFaces Summary 131 | 132 | Find all icosahedron faces intersected by a given H3 index 133 | and places them in the List<int> outFaces. 134 | 135 | Faces are represented as integers from 0-19, inclusive. 136 | The array is sparse, and empty (no intersection) array 137 | values are represented by -1. 138 | maxFaceCount 139 | 140 | ### H3GetFaces Parameters 141 | 142 | | Name | Type | Description | 143 | |------|------|-------------| 144 | | h3 | H3Lib.H3Index | H3Index cell under investigation | 145 | | outFaces | **out** List<int> | List of faces overlapped by H3Index cell | 146 | 147 | ## MaxFaceCount 148 | 149 | ```c# 150 | int Api.MaxFaceOunt(H3Index h3) 151 | ``` 152 | 153 | ### MaxFaceCount Summary 154 | 155 | Returns the maximum number of icosahedron faces the given H3 156 | index may intersect. 157 | 158 | ### MaxFaceCount Parameters 159 | 160 | | Name | Type | Description | 161 | |------|------|-------------| 162 | | h3 | H3Lib.H3Index | H3Index cell being examined | 163 | 164 |
165 | 166 | [Return to Uber API Table of Contents](Uber-Api.md) 167 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # H3NET: A Hexagonal Hierarchical Geospatial Indexing System In C# # 2 | | | | 3 | |---|---| 4 | | H3Net is a geospatial indexing system using hexagonal grid that can be (approximately) subdivided into finer and finer hexagonal grids, combining the benefits of a hexagonal grid with [S2](https://code.google.com/archive/p/s2-geometry-library/) hierarchical subdivisions, mostly translated from the original C code from [Uber's](https://github.com/uber) [H3](https://github.com/uber/h3) project.| ![h3net logo](./h3net.300.png)| 5 | 6 | 7 | 8 | ## Why? There's already a version in C! 9 | 10 | The short version: 11 | 12 | I'm working on a project that needs H3 capabilities in C#. When I first started this, I could 13 | make the bindings work on desktop, but not mobile, so I wrote the 3.1.1 version. Working with 14 | it in my project over the past couple of years, along with some of the new things in H3, I 15 | came back to it to work with 3.7.1 capabilities. 16 | 17 | That's what's here now. I'm still working on it, but it's now at a usable, albeit 18 | [lightly documented](H3Lib/Documentation) state. You really should check out the 19 | H3 link I placed before, if you want to know what's involved here. 20 | 21 | ## History 22 | * Current (3rd round) - Currently has the same capability as 23 | 3.7.2, if not exactly the same syntax. 24 | * Version 2 - A horrible attempt to implement H3 v3.2.1, and 25 | I've removed the branch for it. 26 | * Version 1 - According to my Git repository, I pushed it on 27 | November 16, 2018. It has the capability of Uber H3 3.1.1, 28 | but it has a [horrible API](OldApi.md). 29 | 30 | ## Caveat Emptor 31 | This doesn't work *exactly* like H3, especially under the hood, 32 | but it's close enough for most work, I feel. 33 | 34 | Right now, you can probably work your way through the syntax in 35 | Api.cs in the lib directory. For actual use, I've got a fluid 36 | API to chain commands together, though most of that is being 37 | used internally. 38 | 39 | I'm also going to be closing off access to the internals shortly 40 | so that functionality reflecting the API will be the only direct 41 | access to the code. 42 | 43 | At this point, the only comprehensive documentation is the auto-generated 44 | file at [H3Lib/Documentation](H3Lib/Documentation). Keep an eye on that 45 | directory as I'll be cleaning that up next. 46 | 47 | ## Input 48 | I'm going to be doing the following: 49 | * Cleaning up the code, including some of the brute force translations 50 | * Extracting/writing documentation for the library 51 | * Probably adding a few unit tests for the modifications I've made. 52 | 53 | You can help too. Fork and make a PR, and we'll go from there. 54 | 55 | ## Caveat Emptor II 56 | I wanted to get this done in a month. I have. It's **nowhere** near 57 | the polished state I want it to be, but it works, and it passes 200+ 58 | unit tests. It's now ready to be played with. 59 | 60 | Don't go crazy with it just yet, as I'm going to clean it up some more, 61 | work on documentation, deal with TODO's in the code, and so forth. At 62 | some point after that, I'll have an actual release. 63 | 64 | ## Testing 65 | For the most part, I've converted the unit tests from the original H3 66 | project to work in h3net. They were extremely helpful with the 67 | architecture change going from 3.1.1 to 3.7.1. 68 | 69 | Some corner cases weren't convertible as they tested for null objects 70 | while h3net uses primarily extension methods on readonly structs. 71 | Where this has come up, I've documented it in the appropriate unit test 72 | file. 73 | 74 | ## Version 75 | I will be keeping the version number the same as the functionality of 76 | H3 that I'm matching. 77 | 78 | Currently: **3.7.2** 79 | 80 | Previous: **3.7.1** 81 | 82 | ## Badges 83 | ![.NET](https://github.com/RichardVasquez/h3net/workflows/.NET/badge.svg) 84 | [![Maintainability](https://api.codeclimate.com/v1/badges/ed65501f16bda4b50200/maintainability)](https://codeclimate.com/github/RichardVasquez/h3net/maintainability) 85 | ![JetBrains Rider](https://img.shields.io/badge/-Rider-blue?style=flat&logo=JetBrains) 86 | ![H3 3.7.2](https://img.shields.io/badge/H3-3.7.2-brightgreen) 87 | ![Burma Shave](https://img.shields.io/badge/Burma-Shave-brightgreen) 88 | ## Fin 89 | [Hexagons are the bestagons](https://www.youtube.com/watch?v=thOifuHs6eY) 90 | -------------------------------------------------------------------------------- /H3Lib/Vec2d.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using DecimalMath; 4 | 5 | namespace H3Lib 6 | { 7 | /// 8 | /// 2D floating point vector functions. 9 | /// 10 | [DebuggerDisplay("X: {X} Y: {Y}")] 11 | public readonly struct Vec2d:IEquatable 12 | { 13 | /// 14 | /// X coordinate 15 | /// 16 | public readonly decimal X; 17 | /// 18 | /// Y Coordinate 19 | /// 20 | public readonly decimal Y; 21 | 22 | /// 23 | /// Constructor 24 | /// 25 | public Vec2d(decimal x, decimal y) 26 | { 27 | X = x; 28 | Y = y; 29 | } 30 | 31 | /// 32 | /// Calculates the magnitude of a 2D cartesian vector. 33 | /// 34 | /// 38 | public decimal Magnitude => DecimalEx.Sqrt(X * X + Y * Y); 39 | 40 | /** 41 | * Finds the intersection between two lines. Assumes that the lines intersect 42 | * and that the intersection is not at an endpoint of either line. 43 | * @param p0 The first endpoint of the first line. 44 | * @param p1 The second endpoint of the first line. 45 | * @param p2 The first endpoint of the second line. 46 | * @param p3 The second endpoint of the second line. 47 | * @param inter The intersection point. 48 | */ 49 | /// 50 | /// Finds the intersection between two lines. Assumes that the lines intersect 51 | /// and that the intersection is not at an endpoint of either line. 52 | /// 53 | /// The first endpoint of the first line 54 | /// The second endpoint of the first line 55 | /// The first endpoint of the second line 56 | /// The first endpoint of the first line 57 | /// The intersection point. 58 | /// 62 | public static Vec2d FindIntersection(Vec2d p0, Vec2d p1, Vec2d p2, Vec2d p3) 63 | { 64 | var s1 = new Vec2d(p1.X - p0.X, p1.Y - p0.Y); 65 | var s2 = new Vec2d(p3.X - p2.X, p3.Y - p2.Y); 66 | 67 | decimal t = (s2.X * (p0.Y - p2.Y) - s2.Y * (p0.X - p2.X)) / 68 | (-s2.X * s1.Y + s1.X * s2.Y); 69 | 70 | return new Vec2d 71 | ( 72 | p0.X + (t * s1.X), 73 | p0.Y + (t * s1.Y) 74 | ); 75 | } 76 | 77 | /// 78 | /// Equality test 79 | /// 80 | public bool Equals(Vec2d other) 81 | { 82 | return 83 | Math.Abs(X - other.X) < Constants.H3.DBL_EPSILON && 84 | Math.Abs(Y - other.Y) < Constants.H3.DBL_EPSILON; 85 | 86 | } 87 | 88 | /// 89 | /// Equality test against unboxed object 90 | /// 91 | public override bool Equals(object obj) 92 | { 93 | return obj is Vec2d other && Equals(other); 94 | } 95 | 96 | /// 97 | /// Hashcode for identity 98 | /// 99 | /// 100 | public override int GetHashCode() 101 | { 102 | return HashCode.Combine(X, Y); 103 | } 104 | 105 | /// 106 | /// Equality operator 107 | /// 108 | public static bool operator ==(Vec2d left, Vec2d right) 109 | { 110 | return left.Equals(right); 111 | } 112 | 113 | /// 114 | /// Inequality operator 115 | /// 116 | public static bool operator !=(Vec2d left, Vec2d right) 117 | { 118 | return !left.Equals(right); 119 | } 120 | 121 | /// 122 | /// Debug info as string 123 | /// 124 | public override string ToString() 125 | { 126 | return $"Vec2d: X: {X} Y: {Y}"; 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3UniEdgeExhaustive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using H3Lib; 4 | using H3Lib.Extensions; 5 | using NUnit.Framework; 6 | using TestSuite.Lib; 7 | 8 | namespace TestSuite 9 | { 10 | [TestFixture] 11 | public class TestH3UniEdgeExhaustive 12 | { 13 | private static void H3UniEdgeCorrectnessAssertions(H3Index h3) 14 | { 15 | bool isPentagon = h3.IsPentagon(); 16 | var edges = h3.GetUniEdgesFromCell(); 17 | 18 | for (var i = 0; i < 6; i++) 19 | { 20 | if (isPentagon && i == 0) 21 | { 22 | Assert.AreEqual(Constants.H3Index.H3_NULL, edges[i]); 23 | continue; 24 | } 25 | 26 | Assert.IsTrue(edges[i].IsValidUniEdge()); 27 | Assert.AreEqual(h3, edges[i].OriginFromUniDirectionalEdge()); 28 | var destination = edges[i].DestinationFromUniDirectionalEdge(); 29 | Assert.IsTrue(h3.IsNeighborTo(destination)); 30 | } 31 | } 32 | 33 | private static void H3UniEdgeBoundaryAssertions(H3Index h3) 34 | { 35 | var edges = h3.GetUniEdgesFromCell(); 36 | 37 | for (var i = 0; i < 6; i++) 38 | { 39 | if (edges[i] == Constants.H3Index.H3_NULL) 40 | { 41 | continue; 42 | } 43 | 44 | var edgeBoundary = edges[i].UniEdgeToGeoBoundary(); 45 | var destination = edges[i].DestinationFromUniDirectionalEdge(); 46 | var revEdge = destination.UniDirectionalEdgeTo(h3); 47 | 48 | var revEdgeBoundary = revEdge.UniEdgeToGeoBoundary(); 49 | 50 | Assert.AreEqual(edgeBoundary.NumVerts, revEdgeBoundary.NumVerts); 51 | 52 | for (var j = 0; j < edgeBoundary.NumVerts; j++) 53 | { 54 | Assert.IsTrue 55 | ( 56 | Math.Abs( 57 | edgeBoundary.Verts[j].Latitude - 58 | revEdgeBoundary.Verts[revEdgeBoundary.NumVerts - 1 - j].Latitude 59 | ) < 0.000001m, $"{h3.Value} - {h3}" 60 | ); 61 | Assert.IsTrue 62 | ( 63 | Math.Abs 64 | ( 65 | edgeBoundary.Verts[j].Longitude - 66 | revEdgeBoundary.Verts[revEdgeBoundary.NumVerts - 1 - j].Longitude 67 | ) < 68 | 0.000001m 69 | ); 70 | } 71 | } 72 | } 73 | 74 | [Test] 75 | public void H3UniEdgeCorrectness() 76 | { 77 | Utility.IterateAllIndexesAtRes(0, H3UniEdgeCorrectnessAssertions); 78 | Utility.IterateAllIndexesAtRes(1, H3UniEdgeCorrectnessAssertions); 79 | Utility.IterateAllIndexesAtRes(2, H3UniEdgeCorrectnessAssertions); 80 | Utility.IterateAllIndexesAtRes(3, H3UniEdgeCorrectnessAssertions); 81 | Utility.IterateAllIndexesAtRes(4, H3UniEdgeCorrectnessAssertions); 82 | } 83 | 84 | [Test] 85 | public void h3UniEdgeBoundary() 86 | { 87 | Utility.IterateAllIndexesAtRes(0, H3UniEdgeBoundaryAssertions); 88 | Utility.IterateAllIndexesAtRes(1, H3UniEdgeBoundaryAssertions); 89 | Utility.IterateAllIndexesAtRes(2, H3UniEdgeBoundaryAssertions); 90 | Utility.IterateAllIndexesAtRes(3, H3UniEdgeBoundaryAssertions); 91 | Utility.IterateAllIndexesAtRes(4, H3UniEdgeBoundaryAssertions); 92 | // Res 5: normal base cell 93 | Utility.IterateBaseCellIndexesAtRes(5, H3UniEdgeBoundaryAssertions, 0); 94 | // Res 5: pentagon base cell 95 | Utility.IterateBaseCellIndexesAtRes(5, H3UniEdgeBoundaryAssertions, 14); 96 | // Res 5: polar pentagon base cell 97 | Utility.IterateBaseCellIndexesAtRes(5, H3UniEdgeBoundaryAssertions, 117); 98 | // Res 6: Test one pentagon just to check for new edge cases 99 | Utility.IterateBaseCellIndexesAtRes(6, H3UniEdgeBoundaryAssertions, 14); 100 | } 101 | 102 | 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3GetFaces.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | using NUnit.Framework; 5 | using TestSuite.Lib; 6 | 7 | namespace TestSuite 8 | { 9 | [TestFixture] 10 | public class TestH3GetFaces 11 | { 12 | private static int CountFaces(H3Index h3) 13 | { 14 | var faces = h3.GetFaces(); 15 | return faces.Count(face => face >= 0 && face <= 19); 16 | } 17 | 18 | private static void AssertSingleHexFace(H3Index h3) 19 | { 20 | int validCount = CountFaces(h3); 21 | Assert.AreEqual(1,validCount); 22 | } 23 | 24 | private void AssertMultipleHexFaces(H3Index h3) 25 | { 26 | int validCount = CountFaces(h3); 27 | Assert.AreEqual(2, validCount); 28 | } 29 | 30 | private void AssertPentagonFaces(H3Index h3) 31 | { 32 | Assert.IsTrue(h3.IsPentagon()); 33 | int validCount = CountFaces(h3); 34 | Assert.AreEqual(5, validCount); 35 | } 36 | 37 | [Test] 38 | public void SingleFaceHexes() 39 | { 40 | // base cell 16 is at the center of an icosahedron face, 41 | // so all children should have the same face 42 | Utility.IterateBaseCellIndexesAtRes(2, AssertSingleHexFace, 16); 43 | Utility.IterateBaseCellIndexesAtRes(3, AssertSingleHexFace, 16); 44 | } 45 | 46 | [Test] 47 | public void HexagonWithEdgeVertices() 48 | { 49 | // Class II pentagon neighbor - one face, two adjacent vertices on edge 50 | H3Index h3 = 0x821c37fffffffff; 51 | AssertSingleHexFace(h3); 52 | } 53 | 54 | [Test] 55 | public void HexagonWithDistortion() 56 | { 57 | // Class III pentagon neighbor, distortion across faces 58 | H3Index h3 = 0x831c06fffffffff; 59 | AssertMultipleHexFaces(h3); 60 | } 61 | 62 | [Test] 63 | public void HexagonCrossingFaces() 64 | { 65 | // Class II hex with two vertices on edge 66 | H3Index h3 = 0x821ce7fffffffff; 67 | AssertMultipleHexFaces(h3); 68 | } 69 | 70 | [Test] 71 | public void ClassIiiPentagon() 72 | { 73 | H3Index pentagon = new H3Index(1, 4, 0); 74 | AssertPentagonFaces(pentagon); 75 | } 76 | 77 | [Test] 78 | public void ClassIiPentagon() 79 | { 80 | H3Index pentagon = new H3Index(2, 4, 0); 81 | AssertPentagonFaces(pentagon); 82 | } 83 | 84 | [Test] 85 | public void Res15Pentagon() 86 | { 87 | H3Index pentagon = new H3Index(15, 4, 0); 88 | AssertPentagonFaces(pentagon); 89 | } 90 | 91 | [Test] 92 | public void BaseCellHexagons() 93 | { 94 | int singleCount = 0; 95 | int multipleCount = 0; 96 | for (int i = 0; i < Constants.H3.NUM_BASE_CELLS; i++) 97 | { 98 | if (!i.IsBaseCellPentagon()) 99 | { 100 | // Make the base cell index 101 | H3Index baseCell = new H3Index(0, i, 0); 102 | int validCount = CountFaces(baseCell); 103 | Assert.Greater(validCount, 0); 104 | if (validCount == 1) 105 | { 106 | singleCount++; 107 | } 108 | else 109 | { 110 | multipleCount++; 111 | } 112 | } 113 | } 114 | Assert.AreEqual(80,singleCount); 115 | Assert.AreEqual(30,multipleCount); 116 | } 117 | 118 | [Test] 119 | public void BaseCellPentagons() 120 | { 121 | for (var i = 0; i < Constants.H3.NUM_BASE_CELLS; i++) 122 | { 123 | if (!i.IsBaseCellPentagon()) 124 | { 125 | continue; 126 | } 127 | // Make the base cell index 128 | var baseCell = new H3Index(0, i, 0); 129 | AssertPentagonFaces(baseCell); 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3ToChildren.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | using NUnit.Framework; 5 | 6 | namespace TestSuite 7 | { 8 | [TestFixture] 9 | public class TestH3ToChildren 10 | { 11 | private const int PADDED_COUNT = 10; 12 | 13 | private static readonly GeoCoord sf = new GeoCoord(0.659966917655m, 2 * 3.14159m - 2.1364398519396m); 14 | private static readonly H3Index sfHex8 = sf.ToH3Index(8); 15 | 16 | private void verifyCountAndUniqueness 17 | (List children, int expectedCount) 18 | { 19 | int numFound = 0; 20 | for (int i = 0; i < children.Count; i++) 21 | { 22 | H3Index currIndex = children[i]; 23 | if (currIndex == 0) 24 | { 25 | continue; 26 | } 27 | numFound++; 28 | // verify uniqueness 29 | int indexSeen = 0; 30 | for (int j = i + 1; j < children.Count; j++) 31 | { 32 | if (children[j] == currIndex) { 33 | indexSeen++; 34 | } 35 | } 36 | Assert.AreEqual(0, indexSeen); 37 | } 38 | Assert.AreEqual(expectedCount,numFound); 39 | } 40 | 41 | [Test] 42 | public void OneResStep() 43 | { 44 | const int expectedCount = 7; 45 | //const int paddedCount = 10; 46 | 47 | var sfHex9s = sfHex8.ToChildren(9); 48 | var center = sfHex8.ToGeoCoord(); 49 | var sfHex9_0 = center.ToH3Index(9); 50 | 51 | int numFound = 0; 52 | // Find the center 53 | for (int i = 0; i < sfHex9s.Count; i++) { 54 | if (sfHex9_0 == sfHex9s[i]) { 55 | numFound++; 56 | } 57 | } 58 | Assert.AreEqual(1,numFound); 59 | 60 | // Get the neighbor hexagons by averaging the center point and outer 61 | // points then query for those independently 62 | 63 | GeoBoundary outside = sfHex8.ToGeoBoundary(); 64 | 65 | for (int i = 0; i < outside.NumVerts; i++) 66 | { 67 | GeoCoord avg = new GeoCoord 68 | ( 69 | (outside.Verts[i].Latitude + center.Latitude) / 2, 70 | (outside.Verts[i].Longitude + center.Longitude) / 2 71 | ); 72 | H3Index avgHex9 = avg.ToH3Index(9); 73 | for (int j = 0; j < expectedCount; j++) { 74 | if (avgHex9 == sfHex9s[j]) { 75 | numFound++; 76 | } 77 | } 78 | } 79 | 80 | Assert.AreEqual(expectedCount,numFound); 81 | } 82 | 83 | [Test] 84 | public void multipleResSteps() 85 | { 86 | // Lots of children. Will just confirm number and uniqueness 87 | const int expectedCount = 49; 88 | var children = sfHex8.ToChildren(10); 89 | verifyCountAndUniqueness(children, expectedCount); 90 | } 91 | 92 | [Test] 93 | public void SameRes() 94 | { 95 | const int expectedCount = 1; 96 | var children = sfHex8.ToChildren(8); 97 | verifyCountAndUniqueness(children, expectedCount); 98 | } 99 | 100 | [Test] 101 | public void ChildResTooCoarse() 102 | { 103 | const int expectedCount = 0; 104 | var children = sfHex8.ToChildren(7); 105 | verifyCountAndUniqueness(children, expectedCount); 106 | } 107 | 108 | [Test] 109 | public void ChildResTooFine() 110 | { 111 | const int expectedCount = 0; 112 | var sfHexMax = sf.ToH3Index(Constants.H3.MAX_H3_RES); 113 | var children = sfHexMax.ToChildren(Constants.H3.MAX_H3_RES + 1); 114 | verifyCountAndUniqueness(children, expectedCount); 115 | } 116 | 117 | [Test] 118 | public void PentagonChildren() 119 | { 120 | H3Index pentagon = new H3Index(1, 4, 0); 121 | 122 | const int expectedCount = (5 * 7) + 6; 123 | var children = sfHex8.ToChildren(10); 124 | children = pentagon.ToChildren(3); 125 | 126 | verifyCountAndUniqueness(children, expectedCount); 127 | } 128 | 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /H3Lib/Documentation/Uber-Api-Unidirectional-Edge.md: -------------------------------------------------------------------------------- 1 | # Unidirectional Edge Functions 2 | 3 | Unidirectional edges allow encoding the directed edge from one cell to a neighboring cell. 4 | 5 | ## H3IndexesAreNeighbors 6 | 7 | ```c# 8 | int Api.H3IndexesAreNeighbors(H3Index origin, H3Index destination) 9 | ``` 10 | 11 | ### H3IndexesAreNeighbors Summary 12 | 13 | Returns whether or not the provided H3Indexes are neighbors. 14 | 15 | Returns 1 if the indexes are neighbors, 0 otherwise. 16 | 17 | ### H3IndexesAreNeighbors Parameters 18 | 19 | | Name | Type | Description | 20 | |------|------|-------------| 21 | |origin|H3Lib.H3Index|Origin looking for neighbor| 22 | |destination|H3Lib.H3Index|Cell being tested if neighbor| 23 | 24 | ## GetH3UnidirectionalEdge 25 | 26 | ```c# 27 | H3Index Api.GetH3UnidirectionalEdge(H3Index origin, H3Index destination) 28 | ``` 29 | 30 | ### GetH3UnidirectionalEdge Summary 31 | 32 | Returns a unidirectional edge H3 index based on the provided origin and 33 | destination. 34 | 35 | Returns 0 on error. 36 | 37 | ### GetH3UnidirectionalEdge Parameters 38 | 39 | | Name | Type | Description | 40 | |------|------|-------------| 41 | |origin|H3Lib.H3Index|Origin cell for edge| 42 | |destination|H3LIb.H3Index|Destination cell for edge| 43 | 44 | ## H3UnidirectionalEdgeIsValid 45 | 46 | ```c# 47 | int Api.H3UnidirectionalEdgeIsValid(H3Index edge) 48 | ``` 49 | 50 | ### H3UnidirectionalEdgeIsValid Summary 51 | 52 | Determines if the provided H3Index is a valid unidirectional edge index. 53 | 54 | Returns 1 if it is a unidirectional edge H3Index, otherwise 0. 55 | 56 | ### H3UnidirectionalEdgeIsValid Parameters 57 | 58 | | Name | Type | Description | 59 | |------|------|-------------| 60 | |edge|H3Lib.H3Index|Unidirectional edge being tested| 61 | 62 | ## GetOriginH3IndexFromUnidirectionalEdge 63 | 64 | ```c# 65 | H3Index Api.GetOriginH3IndexFromUnidirectionalEdge(H3Index edge) 66 | ``` 67 | 68 | ### GetOriginH3IndexFromUnidirectionalEdge Summary 69 | 70 | Returns the origin hexagon from the unidirectional edge H3Index. 71 | 72 | ### GetOriginH3IndexFromUnidirectionalEdge Parameters 73 | 74 | | Name | Type | Description | 75 | |------|------|-------------| 76 | |edge|H3Lib.H3Index|Edge to find origin cell| 77 | 78 | ## GetDestinationH3IndexFromUnidirectionalEdge 79 | 80 | ```c# 81 | H3Index Api.GetDestinationH3IndexFromUnidirectionalEdge(H3Index edge) 82 | ``` 83 | 84 | ### GetDestinationH3IndexFromUnidirectionalEdge Summary 85 | 86 | Returns the destination hexagon from the unidirectional edge H3Index. 87 | 88 | ### GetDestinationH3IndexFromUnidirectionalEdge Parameters 89 | 90 | | Name | Type | Description | 91 | |------|------|-------------| 92 | |edge|H3Lib.H3Index|Edge to find destination cell| 93 | 94 | ## GetH3IndexesFromUnidirectionalEdge 95 | 96 | ```c# 97 | void Api.GetH3IndexesFromUnidirectionalEdge(H3Index edge, out (H3Index, H3Index) originDestination) 98 | ``` 99 | 100 | ### GetH3IndexesFromUnidirectionalEdge Summary 101 | 102 | Returns the origin, destination pair of hexagon IDs for the given edge ID, which are placed at originDestination.Item1 and originDestination.Item2 respectively. 103 | 104 | ### GetH3IndexesFromUnidirectionalEdge Parameters 105 | 106 | | Name | Type | Description | 107 | |------|------|-------------| 108 | |edge|H3Lib.H3Index|Edge to find origin and destination cells| 109 | |originDestination|**out** Tuple<H3Lib.H3Index, H3Lib.H3Index>|paired origin/destination H3Index cells for edge| 110 | 111 | ## GetH3UnidirectionalEdgesFromHexagon 112 | 113 | ```c# 114 | void Api.GetH3UnidirectionalEdgesFromHexagon(H3Index origin, out List edges) 115 | ``` 116 | 117 | ### GetH3UnidirectionalEdgesFromHexagon Summary 118 | 119 | Provides all of the unidirectional edges from the current H3Index. The number of undirectional edges placed in edges may be less than 6. 120 | 121 | ### GetH3UnidirectionalEdgesFromHexagon Parameters 122 | 123 | | Name | Type | Description | 124 | |------|------|-------------| 125 | |origin|H3Lib.H3Index|Origin cell to get edges from| 126 | |edges|**out** List<H3Lib.H3Index>|The edges originating from the cell| 127 | 128 | ## GetH3UnidirectionalEdgeBoundary 129 | 130 | ```c# 131 | void Api.GetH3UnidirectionalEdgeBoundary(H3Index edge, out GeoBoundary gb) 132 | ``` 133 | 134 | ### GetH3UnidirectionalEdgeBoundary Summary 135 | 136 | Provides the coordinates defining the unidirectional edge. 137 | 138 | ### GetH3UnidirectionalEdgeBoundary Parameters 139 | 140 | | Name | Type | Description | 141 | |------|------|-------------| 142 | |edge|H3Lib.H3Index|Edge to find coordinates of| 143 | |gb|**out** H3Lib.GeoBoundary|The geoboundary that defines the edge in terms of vertices| 144 | 145 |
146 | 147 | [Return to Uber API Table of Contents](Uber-Api.md) 148 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestH3Api.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using H3Lib; 3 | using H3Lib.Extensions; 4 | using NUnit.Framework; 5 | 6 | namespace TestSuite 7 | { 8 | [TestFixture] 9 | public class TestH3Api 10 | { 11 | [Test] 12 | public void GeoToH3Res() 13 | { 14 | GeoCoord anywhere = default; 15 | Assert.AreEqual(Constants.H3Index.H3_NULL, Api.GeoToH3(anywhere, -1)); 16 | Assert.AreEqual(Constants.H3Index.H3_NULL, Api.GeoToH3(anywhere, 16)); 17 | } 18 | 19 | [Test] 20 | public void GeoToH3Coord() 21 | { 22 | // Test removed for now as decimals don't have NaN or infinities 23 | // GeoCoord invalidLat = new GeoCoord(double.NaN, 0); 24 | // GeoCoord invalidLon = new GeoCoord(0, double.NaN); 25 | // GeoCoord invalidLatLon = new GeoCoord(double.PositiveInfinity, double.NegativeInfinity); 26 | // 27 | // Assert.AreEqual(Constants.H3Index.H3_NULL, Api.GeoToH3(invalidLat, 1)); 28 | // Assert.AreEqual(Constants.H3Index.H3_NULL, Api.GeoToH3(invalidLon, 1)); 29 | // Assert.AreEqual(Constants.H3Index.H3_NULL, Api.GeoToH3(invalidLatLon, 1)); 30 | } 31 | 32 | // Bug test for https://github.com/uber/h3/issues/45 33 | [Test] 34 | public void h3ToGeoBoundary_classIIIEdgeVertex() 35 | { 36 | var hexes = new string[] 37 | { 38 | "894cc5349b7ffff", "894cc534d97ffff", "894cc53682bffff", 39 | "894cc536b17ffff", "894cc53688bffff", "894cead92cbffff", 40 | "894cc536537ffff", "894cc5acbabffff", "894cc536597ffff" 41 | }; 42 | for (int i = 0; i < hexes.Length; i++) 43 | { 44 | var h3 = Api.StringToH3(hexes[i]); 45 | var b = h3.ToGeoBoundary(); 46 | Assert.AreEqual(7, b.NumVerts, $"{h3.Value} => {h3.ToString()}"); 47 | } 48 | } 49 | 50 | [Test] 51 | // Bug test for https://github.com/uber/h3/issues/45 52 | public void H3ToGeoBoundary_classIIIEdgeVertex_exact() 53 | { 54 | H3Index h3 = Api.StringToH3("894cc536537ffff"); 55 | GeoBoundary boundary = new GeoBoundary(); 56 | boundary.NumVerts = 7; 57 | 58 | boundary.Verts[0] = Api.SetGeoDegs( 18.043333154m, -66.27836523500002m); 59 | boundary.Verts[1] = Api.SetGeoDegs( 18.042238363m, -66.27929062800001m); 60 | boundary.Verts[2] = Api.SetGeoDegs( 18.040818259m, -66.27854193899998m); 61 | boundary.Verts[3] = Api.SetGeoDegs( 18.040492975m, -66.27686786700002m); 62 | boundary.Verts[4] = Api.SetGeoDegs( 18.041040385m, -66.27640518300001m); 63 | boundary.Verts[5] = Api.SetGeoDegs( 18.041757122m, -66.27596711500001m); 64 | boundary.Verts[6] = Api.SetGeoDegs( 18.043007860m, -66.27669118199998m); 65 | 66 | GeoBoundary myBoundary; 67 | Api.H3ToGeoBoundary(h3, out myBoundary); 68 | Assert.AreEqual(boundary.NumVerts,myBoundary.NumVerts); 69 | 70 | for (int i = 0; i < boundary.NumVerts; i++) 71 | { 72 | Assert.AreEqual(boundary.Verts[0], myBoundary.Verts[0]); 73 | } 74 | } 75 | 76 | // Bug test for https://github.com/uber/h3/issues/212 77 | [Test] 78 | public void H3ToGeoBoundary_coslonConstrain() 79 | { 80 | H3Index h3 = 0x87dc6d364ffffffL; 81 | GeoBoundary boundary = new GeoBoundary(); 82 | boundary.NumVerts = 6; 83 | boundary.Verts[0] = Api.SetGeoDegs( -52.0130533678236091m, -34.6232931343713091m); 84 | boundary.Verts[1] = Api.SetGeoDegs( -52.0041156384652012m, -34.6096733160584549m); 85 | boundary.Verts[2] = Api.SetGeoDegs( -51.9929610229502472m, -34.6165157145896387m); 86 | boundary.Verts[3] = Api.SetGeoDegs( -51.9907410568096608m, -34.6369680004259877m); 87 | boundary.Verts[4] = Api.SetGeoDegs( -51.9996738734672377m, -34.6505896528323660m); 88 | boundary.Verts[5] = Api.SetGeoDegs( -52.0108315681413629m, -34.6437571897165668m); 89 | 90 | GeoBoundary myBoundary; 91 | Api.H3ToGeoBoundary(h3, out myBoundary); 92 | Assert.AreEqual(boundary.NumVerts,myBoundary.NumVerts); 93 | 94 | for (int i = 0; i < boundary.NumVerts; i++) 95 | { 96 | Assert.AreEqual(boundary.Verts[0], myBoundary.Verts[0]); 97 | } 98 | } 99 | 100 | [Test] 101 | public void Version() 102 | { 103 | Assert.GreaterOrEqual(Constants.H3_VERSION_MAJOR, 0); 104 | Assert.GreaterOrEqual(Constants.H3_VERSION_MINOR, 0); 105 | Assert.GreaterOrEqual(Constants.H3_VERSION_PATCH, 0); 106 | } 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToGeo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using AppsLib; 6 | using CommandLineParser.Arguments; 7 | using H3Lib; 8 | using H3Lib.Extensions; 9 | 10 | namespace H3ToGeo 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | using var parser = new CommandLineParser.CommandLineParser(); 17 | 18 | args = args.Select(s => s.ToLower()).ToArray(); 19 | 20 | try 21 | { 22 | var argParser = new H3ToGeoArguments(); 23 | parser.ExtractArgumentAttributes(argParser); 24 | parser.ParseCommandLine(args); 25 | 26 | if (string.IsNullOrEmpty(argParser.InputFileName) && argParser.Index == 0) 27 | { 28 | Console.WriteLine("Need either a file or an H3Index to continue"); 29 | parser.ShowUsage(); 30 | } 31 | else if(!string.IsNullOrEmpty(argParser.InputFileName) && argParser.Index != 0) 32 | { 33 | Console.WriteLine("Cannot take both a file name and an index"); 34 | parser.ShowUsage(); 35 | } 36 | 37 | if (string.IsNullOrEmpty(argParser.InputFileName)) 38 | { 39 | ProcessIndex(argParser,parser); 40 | } 41 | else 42 | { 43 | ProcessFile(argParser, parser); 44 | } 45 | } 46 | catch (Exception) 47 | { 48 | Console.WriteLine("Unable to parse input."); 49 | parser.ShowUsage(); 50 | } 51 | } 52 | 53 | private static void ProcessFile(H3ToGeoArguments argParser, CommandLineParser.CommandLineParser parser) 54 | { 55 | if (!File.Exists(argParser.InputFileName)) 56 | { 57 | Console.WriteLine($"Unable to open {argParser.InputFileName}"); 58 | parser.ShowUsage(); 59 | return; 60 | } 61 | 62 | try 63 | { 64 | var lines = File.ReadLines(argParser.InputFileName); 65 | foreach (string line in lines) 66 | { 67 | bool test = ulong.TryParse(line.Trim(), out ulong value); 68 | if (!test) 69 | { 70 | continue; 71 | } 72 | var h3 = new H3Index(value); 73 | Console.Write(DoCell(h3, argParser)); 74 | } 75 | } 76 | catch (Exception e) 77 | { 78 | Console.WriteLine(e); 79 | throw; 80 | } 81 | 82 | } 83 | 84 | private static string DoCell(H3Index h3, H3ToGeoArguments argParser) 85 | { 86 | var sb = new StringBuilder(); 87 | var gc = h3.ToGeoCoord(); 88 | 89 | if (argParser.Kml) 90 | { 91 | string name = string.IsNullOrEmpty(argParser.KmlName) 92 | ? "H3 Geometry" 93 | : argParser.KmlDescription; 94 | string desc = string.IsNullOrEmpty(argParser.KmlDescription) 95 | ? "Generated by h3ToGeo" 96 | : argParser.KmlDescription; 97 | 98 | sb.Append(Kml.PtsHeader(name, desc)); 99 | sb.Append(Kml.OutputPointKml(gc, h3.ToString())); 100 | sb.Append(Kml.PtsFooter()); 101 | } 102 | else 103 | { 104 | sb.Append($"{gc.Latitude.RadiansToDegrees(),10:f8}, {gc.Longitude.RadiansToDegrees(),10:f8}"); 105 | } 106 | 107 | return sb.ToString(); 108 | } 109 | 110 | private static void ProcessIndex(H3ToGeoArguments argParser, CommandLineParser.CommandLineParser parser) 111 | { 112 | var h3 = new H3Index(argParser.Index); 113 | Console.Write(DoCell(h3, argParser)); 114 | } 115 | 116 | } 117 | 118 | public class H3ToGeoArguments 119 | { 120 | [SwitchArgument("kml", false, Optional = true, Description = "Flag to activate KML output")] 121 | public bool Kml; 122 | 123 | [BoundedValueArgument(typeof(string), "kml-name", Optional = true, Description = "Name of KML")] 124 | public string KmlName; 125 | 126 | [BoundedValueArgument(typeof(string), "kml-description", Optional = true, Description = "KML Description")] 127 | public string KmlDescription; 128 | 129 | [BoundedValueArgument(typeof(string), "file", Optional = true)] 130 | public string InputFileName; 131 | 132 | [BoundedValueArgument(typeof(ulong), "index", Optional = true)] 133 | public ulong Index; 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToComponents/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using CommandLineParser.Arguments; 5 | using H3Lib; 6 | using H3Lib.Extensions; 7 | 8 | namespace H3ToComponents 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | using var parser = new CommandLineParser.CommandLineParser(); 15 | 16 | args = args.Select(s => s.ToLower()).ToArray(); 17 | 18 | SwitchArgument verbose = new SwitchArgument('v', "verbose", "Show verbose debugging info", false); 19 | verbose.Optional = true; 20 | 21 | ValueArgument h3 = new ValueArgument 22 | ('h', "h3index", "H3Index (in hexadecimal) to examine"); 23 | 24 | h3.ConvertValueHandler = value => value.ToH3Index(); 25 | 26 | parser.Arguments.Add(verbose); 27 | parser.Arguments.Add(h3); 28 | 29 | try 30 | { 31 | var argParser = new H3ToComponentsParser(); 32 | parser.ExtractArgumentAttributes(argParser); 33 | parser.ParseCommandLine(args); 34 | 35 | if (h3.Parsed) 36 | { 37 | var data = new H3ToComponentsParser {Verbose = verbose.Value, H3 = h3.Value}; 38 | ProcessData(data); 39 | } 40 | else 41 | { 42 | Console.WriteLine("Unable to parse input."); 43 | parser.ShowUsage(); 44 | } 45 | } 46 | catch (Exception) 47 | { 48 | Console.WriteLine("Unable to parse input."); 49 | parser.ShowUsage(); 50 | } 51 | } 52 | 53 | private static void ProcessData(H3ToComponentsParser data) 54 | { 55 | if (data.Verbose) 56 | { 57 | FancyDump(data.H3); 58 | } 59 | else 60 | { 61 | SimpleDump(data.H3); 62 | } 63 | } 64 | 65 | public static char ResDigitToChar(int d) 66 | { 67 | if (d < 0 || d > 7) 68 | { 69 | return 'x'; 70 | } 71 | 72 | return (char) ('0' + d); 73 | } 74 | 75 | private static void SimpleDump(H3Index h3) 76 | { 77 | var sb = new StringBuilder(); 78 | switch (h3.Mode) 79 | { 80 | case H3Mode.Hexagon: 81 | sb.Append($"{(int)h3.Mode}:{h3.Resolution}:{h3.BaseCell}:"); 82 | for (int i = 1; i <= h3.Resolution;i++) 83 | { 84 | sb.Append(ResDigitToChar((int) h3.GetIndexDigit(i))); 85 | } 86 | 87 | break; 88 | case H3Mode.UniEdge: 89 | sb.Append($"{(int)h3.Mode}:{h3.ReservedBits}:{h3.Resolution}:{h3.BaseCell}:"); 90 | for (int i = 1; i <= h3.Resolution;i++) 91 | { 92 | sb.Append(ResDigitToChar((int) h3.GetIndexDigit(i))); 93 | } 94 | 95 | break; 96 | default: 97 | sb.Append("INVALID INDEX"); 98 | break; 99 | } 100 | 101 | Console.WriteLine(sb.ToString()); 102 | } 103 | 104 | private static void FancyDump(H3Index h3) 105 | { 106 | var modes = new[] 107 | { 108 | "RESERVED", "Hexagon", "Unidirectional Edge", "Invalid", 109 | "Invalid", "Invalid", "Invalid", "Invalid", 110 | "Invalid", "Invalid", "Invalid", "Invalid", 111 | "Invalid", "Invalid", "Invalid", "Invalid" 112 | }; 113 | StringBuilder sb = new StringBuilder(); 114 | sb.AppendLine("╔════════════╗"); 115 | sb.AppendLine($"║ H3Index ║ {h3.ToString()}"); 116 | sb.AppendLine("╠════════════╣"); 117 | sb.AppendLine($"║ Mode ║ {modes[(int) h3.Mode]}, {(int) h3.Mode}"); 118 | sb.AppendLine($"║ Resolution ║ {h3.Resolution}"); 119 | if (h3.Mode == H3Mode.UniEdge) 120 | { 121 | sb.AppendLine($"║ Edge ║ {h3.ReservedBits}"); 122 | } 123 | sb.AppendLine($"║ Base Cell ║ {h3.BaseCell}"); 124 | for (int i = 1; i <= h3.Resolution; i++) 125 | { 126 | sb.AppendLine($"║ {i,2} Child ║ {ResDigitToChar((int)h3.GetIndexDigit(i))}"); 127 | } 128 | 129 | sb.AppendLine("╚════════════╝").AppendLine(); 130 | 131 | Console.WriteLine(sb.ToString()); 132 | } 133 | } 134 | 135 | public class H3ToComponentsParser 136 | { 137 | public bool Verbose; 138 | 139 | public H3Index H3; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Apps/Filters/H3ToGeoBoundary/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using AppsLib; 6 | using CommandLineParser.Arguments; 7 | using H3Lib; 8 | using H3Lib.Extensions; 9 | 10 | namespace H3ToGeoBoundary 11 | { 12 | class Program 13 | { 14 | static void Main(string[] args) 15 | { 16 | using var parser = new CommandLineParser.CommandLineParser(); 17 | 18 | args = args.Select(s => s.ToLower()).ToArray(); 19 | 20 | try 21 | { 22 | var argParser = new H3ToGeoBoundaryArguments(); 23 | parser.ExtractArgumentAttributes(argParser); 24 | parser.ParseCommandLine(args); 25 | 26 | if (string.IsNullOrEmpty(argParser.InputFileName) && argParser.Index == 0) 27 | { 28 | Console.WriteLine("Need either a file or an H3Index to continue"); 29 | parser.ShowUsage(); 30 | } 31 | else if(!string.IsNullOrEmpty(argParser.InputFileName) && argParser.Index != 0) 32 | { 33 | Console.WriteLine("Cannot take both a file name and an index"); 34 | parser.ShowUsage(); 35 | } 36 | 37 | if (string.IsNullOrEmpty(argParser.InputFileName)) 38 | { 39 | ProcessIndex(argParser,parser); 40 | } 41 | else 42 | { 43 | ProcessFile(argParser, parser); 44 | } 45 | } 46 | catch (Exception) 47 | { 48 | Console.WriteLine("Unable to parse input."); 49 | parser.ShowUsage(); 50 | } 51 | } 52 | 53 | private static void ProcessFile(H3ToGeoBoundaryArguments argParser, CommandLineParser.CommandLineParser parser) 54 | { 55 | if (!File.Exists(argParser.InputFileName)) 56 | { 57 | Console.WriteLine($"Unable to open {argParser.InputFileName}"); 58 | parser.ShowUsage(); 59 | return; 60 | } 61 | 62 | try 63 | { 64 | var lines = File.ReadLines(argParser.InputFileName); 65 | foreach (string line in lines) 66 | { 67 | bool test = ulong.TryParse(line.Trim(), out ulong value); 68 | if (!test) 69 | { 70 | continue; 71 | } 72 | var h3 = new H3Index(value); 73 | Console.Write(DoCell(h3, argParser)); 74 | } 75 | } 76 | catch (Exception e) 77 | { 78 | Console.WriteLine(e); 79 | throw; 80 | } 81 | 82 | } 83 | 84 | private static string DoCell(H3Index h3, H3ToGeoBoundaryArguments argParser) 85 | { 86 | var sb = new StringBuilder(); 87 | var gb = h3.ToGeoBoundary(); 88 | 89 | if (argParser.Kml) 90 | { 91 | string name = string.IsNullOrEmpty(argParser.KmlName) 92 | ? "H3 Geometry" 93 | : argParser.KmlDescription; 94 | string desc = string.IsNullOrEmpty(argParser.KmlDescription) 95 | ? "Generated by h3ToGeoBoundary" 96 | : argParser.KmlDescription; 97 | 98 | sb.Append(Kml.PtsHeader(name, desc)); 99 | sb.Append(Kml.OutputBoundaryKML(gb, h3.ToString())); 100 | sb.Append(Kml.PtsFooter()); 101 | } 102 | else 103 | { 104 | sb.AppendLine(h3.ToString()); 105 | sb.Append(Utility.GeoBoundaryPrintLines(gb)); 106 | } 107 | 108 | return sb.ToString(); 109 | } 110 | 111 | private static void ProcessIndex(H3ToGeoBoundaryArguments argParser, CommandLineParser.CommandLineParser parser) 112 | { 113 | var h3 = new H3Index(argParser.Index); 114 | Console.Write(DoCell(h3, argParser)); 115 | } 116 | 117 | } 118 | 119 | public class H3ToGeoBoundaryArguments 120 | { 121 | [SwitchArgument("kml", false, Optional = true, Description = "Flag to activate KML output")] 122 | public bool Kml; 123 | 124 | [BoundedValueArgument(typeof(string), "kml-name", Optional = true, Description = "Name of KML")] 125 | public string KmlName; 126 | 127 | [BoundedValueArgument(typeof(string), "kml-description", Optional = true, Description = "KML Description")] 128 | public string KmlDescription; 129 | 130 | [BoundedValueArgument(typeof(string), "file", Optional = true)] 131 | public string InputFileName; 132 | 133 | [BoundedValueArgument(typeof(ulong), "index", Optional = true)] 134 | public ulong Index; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /H3Lib/Extensions/BaseCellsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace H3Lib.Extensions 5 | { 6 | /// 7 | /// Extension methods for BaseCells 8 | /// 9 | public static class BaseCellsExtensions 10 | { 11 | /// 12 | /// Return whether or not the indicated base cell is a pentagon. 13 | /// 14 | /// 18 | public static bool IsBaseCellPentagon(this int baseCell) 19 | { 20 | if (baseCell < 0 || baseCell >= Constants.H3.NUM_BASE_CELLS) 21 | { 22 | return false; 23 | } 24 | return Constants.BaseCells.BaseCellData[baseCell].IsPentagon == 1; 25 | } 26 | 27 | /// 28 | /// Return the direction from the origin base cell to the neighbor. 29 | /// 30 | /// INVALID_DIGIT if the base cells are not neighbors. 31 | /// 35 | public static Direction GetBaseCellDirection(this int originBaseCell, int neighboringBaseCell) 36 | { 37 | for (var dir = Direction.CENTER_DIGIT; dir < Direction.NUM_DIGITS; dir++) { 38 | int testBaseCell = Constants.BaseCells.BaseCellNeighbors[originBaseCell, (int)dir]; 39 | if (testBaseCell == neighboringBaseCell) 40 | { 41 | return dir; 42 | } 43 | } 44 | 45 | return Direction.INVALID_DIGIT; 46 | } 47 | 48 | /// 49 | /// Return whether the indicated base cell is a pentagon where all 50 | /// neighbors are oriented towards it. 51 | /// 52 | /// 56 | internal static bool IsBaseCellPolarPentagon(this int baseCell) 57 | { 58 | return baseCell == 4 || baseCell == 117; 59 | } 60 | 61 | /// 62 | /// Find the FaceIJK given a base cell. 63 | /// 64 | /// 68 | internal static FaceIjk ToFaceIjk(this int baseCell) 69 | { 70 | return new FaceIjk(Constants.BaseCells.BaseCellData[baseCell].HomeFijk); 71 | } 72 | 73 | /// 74 | /// Given a base cell and the face it appears on, return 75 | /// the number of 60' ccw rotations for that base cell's 76 | /// coordinate system. 77 | /// 78 | /// 79 | /// The number of rotations, or INVALID_ROTATIONS if the base 80 | /// cell is not found on the given face 81 | /// 82 | /// 86 | internal static int ToCounterClockwiseRotate60(this int baseCell, int face) 87 | { 88 | if (face < 0 || face > Constants.H3.NUM_ICOSA_FACES) 89 | { 90 | return Constants.BaseCells.InvalidRotations; 91 | } 92 | 93 | for (var i = 0; i < 3; i++) 94 | { 95 | for (var j = 0; j < 3; j++) 96 | { 97 | for (var k = 0; k < 3; k++) 98 | { 99 | if (Constants.BaseCells.FaceIjkBaseCells[face,i,j,k].BaseCell == baseCell) 100 | { 101 | return Constants.BaseCells.FaceIjkBaseCells[face, i, j, k].CounterClockwiseRotate60; 102 | } 103 | } 104 | } 105 | } 106 | 107 | return Constants.BaseCells.InvalidRotations; 108 | } 109 | 110 | /// 111 | /// Return the neighboring base cell in the given direction. 112 | /// 113 | /// 117 | public static int GetNeighbor(this int baseCell, Direction dir) 118 | { 119 | return Constants.BaseCells.BaseCellNeighbors[baseCell, (int) dir]; 120 | } 121 | 122 | /// 123 | /// Return whether or not the tested face is a cw offset face. 124 | /// 125 | /// 129 | internal static bool IsClockwiseOffset(this int baseCell, int testFace) 130 | { 131 | return Constants.BaseCells.BaseCellData[baseCell].ClockwiseOffsetPentagon[0] == testFace || 132 | Constants.BaseCells.BaseCellData[baseCell].ClockwiseOffsetPentagon[1] == testFace; 133 | } 134 | 135 | /// 136 | /// getRes0Indexes generates all base cells 137 | /// 138 | public static List GetRes0Indexes() 139 | { 140 | var results = new List(); 141 | for (var bc = 0; bc < Constants.H3.NUM_BASE_CELLS; bc++) 142 | { 143 | var baseCell = new H3Index(Constants.H3Index.H3_INIT).SetMode(H3Mode.Hexagon).SetBaseCell(bc); 144 | results.Add(baseCell); 145 | } 146 | 147 | return results; 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /H3Lib/CoordIjk.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace H3Lib 5 | { 6 | /// 7 | /// Header file for CoordIJK functions including conversion from lat/lon 8 | /// 9 | /// 10 | /// References two Vec2d cartesian coordinate systems: 11 | /// 12 | /// 1. gnomonic: face-centered polyhedral gnomonic projection space with 13 | /// traditional scaling and x-axes aligned with the face Class II 14 | /// i-axes 15 | /// 16 | /// 2. hex2d: local face-centered coordinate system scaled a specific H3 grid 17 | /// resolution unit length and with x-axes aligned with the local i-axes 18 | /// 19 | [DebuggerDisplay("IJK: ({I}, {J}, {K})")] 20 | public readonly struct CoordIjk:IEquatable 21 | { 22 | /// 23 | /// I Coordinate 24 | /// 25 | public readonly int I; 26 | 27 | /// 28 | /// J Coordinate 29 | /// 30 | public readonly int J; 31 | 32 | /// 33 | /// K Coordinate 34 | /// 35 | public readonly int K; 36 | 37 | /// 38 | /// IJK hexagon coordinates 39 | /// 40 | public CoordIjk(int i, int j, int k):this() 41 | { 42 | I = i; 43 | J = j; 44 | K = k; 45 | } 46 | 47 | /// 48 | /// Constructor 49 | /// 50 | public CoordIjk(CoordIjk coord) 51 | { 52 | I = coord.I; 53 | J = coord.J; 54 | K = coord.K; 55 | } 56 | 57 | /// 58 | /// Debug information 59 | /// 60 | public override string ToString() 61 | { 62 | return $"CoordIjk (IJK) {I}, {J}, {K}"; 63 | } 64 | 65 | /// 66 | /// Given cube coords as doubles, round to valid integer coordinates. Algorithm 67 | /// from https://www.redblobgames.com/grids/hexagons/#rounding 68 | /// 69 | /// Floating-point I coord 70 | /// Floating-point J coord 71 | /// Floating-point K coord 72 | /// IJK coord struct 73 | /// 77 | public static CoordIjk CubeRound(double i, double j, double k) 78 | { 79 | var ri = (int) Math.Round(i, MidpointRounding.AwayFromZero); 80 | var rj = (int) Math.Round(j, MidpointRounding.AwayFromZero); 81 | var rk = (int) Math.Round(k, MidpointRounding.AwayFromZero); 82 | 83 | double iDiff = Math.Abs(ri - i); 84 | double jDiff = Math.Abs(rj - j); 85 | double kDiff = Math.Abs(rk - k); 86 | 87 | // Round, maintaining valid cube coords 88 | if (iDiff > jDiff && iDiff > kDiff) 89 | { 90 | ri = -rj - rk; 91 | } 92 | else if (jDiff > kDiff) 93 | { 94 | rj = -ri - rk; 95 | } 96 | else 97 | { 98 | rk = -ri - rj; 99 | } 100 | 101 | return new CoordIjk(ri, rj, rk); 102 | } 103 | 104 | /// 105 | /// Equality test 106 | /// 107 | public bool Equals(CoordIjk other) 108 | { 109 | return I == other.I && J == other.J && K == other.K; 110 | } 111 | 112 | /// 113 | /// Equality for unboxed object 114 | /// 115 | public override bool Equals(object obj) 116 | { 117 | if (ReferenceEquals(null, obj)) 118 | { 119 | return false; 120 | } 121 | 122 | return obj is CoordIjk ijk && Equals(ijk); 123 | } 124 | 125 | /// 126 | /// Hashcode for identity 127 | /// 128 | /// 129 | public override int GetHashCode() 130 | { 131 | return HashCode.Combine(I, J, K); 132 | } 133 | 134 | /// 135 | /// Equality operator 136 | /// 137 | public static bool operator ==(CoordIjk left, CoordIjk right) 138 | { 139 | return Equals(left, right); 140 | } 141 | 142 | /// 143 | /// Inequality operator 144 | /// 145 | public static bool operator !=(CoordIjk left, CoordIjk right) 146 | { 147 | return !Equals(left, right); 148 | } 149 | 150 | /// 151 | /// Addition operator 152 | /// 153 | public static CoordIjk operator+(CoordIjk c1,CoordIjk c2) 154 | { 155 | return new CoordIjk(c1.I + c2.I, c1.J + c2.J, c1.K + c2.K); 156 | } 157 | 158 | /// 159 | /// Subtraction operator 160 | /// 161 | public static CoordIjk operator-(CoordIjk c1,CoordIjk c2) 162 | { 163 | return new CoordIjk(c1.I - c2.I, c1.J - c2.J, c1.K - c2.K); 164 | } 165 | 166 | /// 167 | /// Multiply operator for scaling 168 | /// 169 | public static CoordIjk operator *(CoordIjk c, int scalar) 170 | { 171 | return new CoordIjk(c.I * scalar, c.J * scalar, c.K * scalar); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Tests/NUnit/H3Suite/TestHexRing.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using H3Lib; 4 | using H3Lib.Extensions; 5 | using NUnit.Framework; 6 | 7 | namespace TestSuite 8 | { 9 | [TestFixture] 10 | public class TestHexRing 11 | { 12 | private static GeoCoord sf = new GeoCoord(0.659966917655m, 2 * 3.14159m - 2.1364398519396m); 13 | private static H3Index sfHex = sf.ToH3Index(9); 14 | 15 | [Test] 16 | public void IdentityKRing() 17 | { 18 | var status = sfHex.HexRing(0); 19 | Assert.AreEqual(0, status.Item1); 20 | Assert.AreEqual(sfHex, status.Item2[0]); 21 | } 22 | 23 | [Test] 24 | public void Ring1() 25 | { 26 | var expectedK1 = new List 27 | { 28 | 0x89283080ddbffff, 0x89283080c37ffff, 29 | 0x89283080c27ffff, 0x89283080d53ffff, 30 | 0x89283080dcfffff, 0x89283080dc3ffff 31 | }; 32 | 33 | (var err, var k1) = sfHex.HexRing(1); 34 | Assert.AreEqual(0, err); 35 | 36 | foreach (var index in expectedK1) 37 | { 38 | Assert.IsTrue(k1.Contains(index)); 39 | } 40 | } 41 | 42 | [Test] 43 | public void Ring2() 44 | { 45 | var expectedK2 = 46 | new List 47 | { 48 | 0x89283080ca7ffff, 0x89283080cafffff, 0x89283080c33ffff, 49 | 0x89283080c23ffff, 0x89283080c2fffff, 0x89283080d5bffff, 50 | 0x89283080d43ffff, 0x89283080d57ffff, 0x89283080d1bffff, 51 | 0x89283080dc7ffff, 0x89283080dd7ffff, 0x89283080dd3ffff 52 | }; 53 | 54 | (var err, var k2) = sfHex.HexRing(2); 55 | Assert.AreEqual(0, err); 56 | 57 | foreach (var index in expectedK2) 58 | { 59 | Assert.IsTrue(k2.Contains(index)); 60 | } 61 | } 62 | 63 | [Test] 64 | public void NearPentagonRing1() 65 | { 66 | H3Index nearPentagon = 0x837405fffffffff; 67 | (int err, _) = nearPentagon.HexRing(1); 68 | Assert.AreNotEqual(0, err); 69 | } 70 | 71 | [Test] 72 | public void NearPentagonRing2() 73 | { 74 | H3Index nearPentagon = 0x837405fffffffff; 75 | (int err, _) = nearPentagon.HexRing(2); 76 | Assert.AreNotEqual(0, err); 77 | } 78 | 79 | [Test] 80 | public void OnPentagon() 81 | { 82 | var nearPentagon = new H3Index(0, 4, 0); 83 | (int err, _) = nearPentagon.HexRing(2); 84 | Assert.AreNotEqual(0, err); 85 | } 86 | 87 | [Test] 88 | public void hexRing_matches_kRingInternal() 89 | { 90 | for (int res = 0; res < 2; res++) 91 | { 92 | for (int i = 0; i < Constants.H3.NUM_BASE_CELLS; i++) 93 | { 94 | H3Index bc = new H3Index(0, i, 0); 95 | (int stat, List children) = bc.Uncompact(res); 96 | 97 | for (int j = 0; j < children.Count; j++) 98 | { 99 | if (children[j] == 0) 100 | { 101 | continue; 102 | } 103 | 104 | for (int k = 0; k < 3; k++) 105 | { 106 | int ringSz = k != 0 107 | ? 6 * k 108 | : 1; 109 | (var failed, var ring) = children[j].HexRing(k); 110 | 111 | if (failed == 0) 112 | { 113 | var lookup = children[j].KRingInternal(k); 114 | var internalNeighbors = lookup.Keys.ToList(); 115 | var internalDistances = lookup.Values.ToList(); 116 | 117 | int found = 0; 118 | int internalFound = 0; 119 | for (int iRing = 0; iRing < ringSz; iRing++) 120 | { 121 | if (ring[iRing] != 0) 122 | { 123 | found++; 124 | 125 | for (int iInternal = 0; 126 | iInternal < lookup.Count; 127 | iInternal++) 128 | { 129 | if (internalNeighbors[iInternal] == 130 | ring[iRing]) 131 | { 132 | internalFound++; 133 | 134 | Assert.AreEqual(internalDistances[iInternal], k); 135 | 136 | break; 137 | } 138 | } 139 | 140 | Assert.AreEqual(found,internalFound); 141 | } 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | /h3net/PhantomCode/BboxCode.cs 263 | /h3net/PhantomCode/BridgeH3IndexFaceIjk.cs 264 | /h3net/PhantomCode/H3IndexCode.cs 265 | 266 | # Other files 267 | /Scratch/* 268 | h3net.sln.DotSettings.user 269 | h3net.sln.DotSettings 270 | -------------------------------------------------------------------------------- /H3Lib/LinkedGeoPolygon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading; 7 | 8 | namespace H3Lib 9 | { 10 | /// 11 | /// A polygon node in a linked geo structure, part of a linked list. 12 | /// 13 | public class LinkedGeoPolygon 14 | { 15 | /// 16 | /// Linked list of loops that make up the polygon 17 | /// 18 | private readonly LinkedList _geoLoops; 19 | 20 | /// 21 | /// Count of loops in polygon 22 | /// 23 | public int CountLoops => _geoLoops.Count; 24 | /// 25 | /// Gets the count of polygons associated 26 | /// 27 | public int CountPolygons => TotalPolygons(); 28 | /// 29 | /// Returns reference to the first loop 30 | /// 31 | public LinkedGeoLoop First => GetFirst(); 32 | /// 33 | /// Returns reference to the last loop 34 | /// 35 | public LinkedGeoLoop Last => GetLast(); 36 | 37 | /// 38 | /// Returns reference to next polygon 39 | /// 40 | public LinkedGeoPolygon Next; 41 | 42 | /// 43 | /// Returns all linked polygons to this one as a linear list 44 | /// 45 | public ReadOnlyCollection LinkedPolygons => GetPolygons(); 46 | 47 | /// 48 | /// Constructor 49 | /// 50 | public LinkedGeoPolygon() 51 | { 52 | _geoLoops = new LinkedList(); 53 | } 54 | 55 | /// 56 | /// Linear list of all loops in this specific polygon 57 | /// 58 | public List Loops => _geoLoops.ToList(); 59 | 60 | /// 61 | /// This is potentially dangerous, thus why it's 62 | /// a private method and provided as read only. 63 | /// 64 | private ReadOnlyCollection GetPolygons() 65 | { 66 | List temp = new List(); 67 | temp.Add(this); 68 | var next = Next; 69 | while (next != null) 70 | { 71 | temp.Add(next); 72 | next = next.Next; 73 | } 74 | 75 | return temp.AsReadOnly(); 76 | } 77 | 78 | /// 79 | /// Add a new linked loop to the current polygon 80 | /// 81 | /// Reference to loop 82 | public LinkedGeoLoop AddNewLinkedLoop() 83 | { 84 | var loop = new LinkedGeoLoop(); 85 | return AddLinkedLoop(loop); 86 | } 87 | 88 | /// 89 | /// Add an existing linked loop to the current polygon 90 | /// 91 | /// Reference to loop 92 | /// 93 | public LinkedGeoLoop AddLinkedLoop(LinkedGeoLoop loop) 94 | { 95 | _geoLoops.AddLast(loop); 96 | return loop; 97 | } 98 | 99 | /// 100 | /// 101 | /// 102 | public void Destroy() 103 | { 104 | Clear(); 105 | } 106 | 107 | /// 108 | /// Free all the geoloops and propagate to the next polygon until 109 | /// there's no more polygons. 110 | /// 111 | public void Clear() 112 | { 113 | foreach (var loop in _geoLoops) 114 | { 115 | loop.Clear(); 116 | } 117 | 118 | Next?.Clear(); 119 | Next = null; 120 | } 121 | 122 | /// 123 | /// Add a newly constructed polygon to current polygon. 124 | /// 125 | /// Reference to new polygon 126 | /// 127 | public LinkedGeoPolygon AddNewLinkedGeoPolygon() 128 | { 129 | if (Next != null) 130 | { 131 | throw new Exception("polygon.Next must be null"); 132 | } 133 | 134 | Next = new LinkedGeoPolygon(); 135 | return Next; 136 | } 137 | 138 | /// 139 | /// Count the number of polygons in a linked list 140 | /// 141 | private int TotalPolygons() 142 | { 143 | var count = 1; 144 | var next = Next; 145 | while (next != null) 146 | { 147 | count++; 148 | next = next.Next; 149 | } 150 | 151 | return count; 152 | } 153 | 154 | /// 155 | /// Returns first loop if any exist, null otherwise 156 | /// 157 | /// 158 | private LinkedGeoLoop GetFirst() 159 | { 160 | if (_geoLoops == null || _geoLoops.Count < 1) 161 | { 162 | return null; 163 | } 164 | 165 | return _geoLoops.First(); 166 | } 167 | 168 | /// 169 | /// Gets last loop in polygon, null if none exist. 170 | /// 171 | /// 172 | private LinkedGeoLoop GetLast() 173 | { 174 | if (_geoLoops == null || _geoLoops.Count < 1) 175 | { 176 | return null; 177 | } 178 | 179 | return _geoLoops.Last(); 180 | } 181 | 182 | } 183 | } 184 | --------------------------------------------------------------------------------