├── 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.| |
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 | 
84 | [](https://codeclimate.com/github/RichardVasquez/h3net/maintainability)
85 | 
86 | 
87 | 
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 |
--------------------------------------------------------------------------------