├── .gitattributes ├── .gitignore ├── LICENSE ├── NGeoHash.Tests.Unit ├── GeoHashTests.cs └── NGeoHash.Tests.Unit.csproj ├── NGeoHash.sln ├── NGeoHash ├── BoundingBox.cs ├── Coordinates.cs ├── GeoHash.cs ├── GeohashDecodeResult.cs └── NGeoHash.csproj └── README.md /.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 | -------------------------------------------------------------------------------- /.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 | .vscode 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | *.lock.json 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # Visual Studio code coverage results 116 | *.coverage 117 | *.coveragexml 118 | 119 | # NCrunch 120 | _NCrunch_* 121 | .*crunch*.local.xml 122 | nCrunchTemp_* 123 | 124 | # MightyMoose 125 | *.mm.* 126 | AutoTest.Net/ 127 | 128 | # Web workbench (sass) 129 | .sass-cache/ 130 | 131 | # Installshield output folder 132 | [Ee]xpress/ 133 | 134 | # DocProject is a documentation generator add-in 135 | DocProject/buildhelp/ 136 | DocProject/Help/*.HxT 137 | DocProject/Help/*.HxC 138 | DocProject/Help/*.hhc 139 | DocProject/Help/*.hhk 140 | DocProject/Help/*.hhp 141 | DocProject/Help/Html2 142 | DocProject/Help/html 143 | 144 | # Click-Once directory 145 | publish/ 146 | 147 | # Publish Web Output 148 | *.[Pp]ublish.xml 149 | *.azurePubxml 150 | # TODO: Comment the next line if you want to checkin your web deploy settings 151 | # but database connection strings (with potential passwords) will be unencrypted 152 | *.pubxml 153 | *.publishproj 154 | 155 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 156 | # checkin your Azure Web App publish settings, but sensitive information contained 157 | # in these scripts will be unencrypted 158 | PublishScripts/ 159 | 160 | # NuGet Packages 161 | *.nupkg 162 | # The packages folder can be ignored because of Package Restore 163 | **/packages/* 164 | # except build/, which is used as an MSBuild target. 165 | !**/packages/build/ 166 | # Uncomment if necessary however generally it will be regenerated when needed 167 | #!**/packages/repositories.config 168 | # NuGet v3's project.json files produces more ignoreable files 169 | *.nuget.props 170 | *.nuget.targets 171 | 172 | # Microsoft Azure Build Output 173 | csx/ 174 | *.build.csdef 175 | 176 | # Microsoft Azure Emulator 177 | ecf/ 178 | rcf/ 179 | 180 | # Windows Store app package directories and files 181 | AppPackages/ 182 | BundleArtifacts/ 183 | Package.StoreAssociation.xml 184 | _pkginfo.txt 185 | 186 | # Visual Studio cache files 187 | # files ending in .cache can be ignored 188 | *.[Cc]ache 189 | # but keep track of directories ending in .cache 190 | !*.[Cc]ache/ 191 | 192 | # Others 193 | ClientBin/ 194 | ~$* 195 | *~ 196 | *.dbmdl 197 | *.dbproj.schemaview 198 | *.jfm 199 | *.pfx 200 | *.publishsettings 201 | node_modules/ 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | 223 | # Business Intelligence projects 224 | *.rdl.data 225 | *.bim.layout 226 | *.bim_*.settings 227 | 228 | # Microsoft Fakes 229 | FakesAssemblies/ 230 | 231 | # GhostDoc plugin setting file 232 | *.GhostDoc.xml 233 | 234 | # Node.js Tools for Visual Studio 235 | .ntvs_analysis.dat 236 | 237 | # Visual Studio 6 build log 238 | *.plg 239 | 240 | # Visual Studio 6 workspace options file 241 | *.opt 242 | 243 | # Visual Studio LightSwitch build output 244 | **/*.HTMLClient/GeneratedArtifacts 245 | **/*.DesktopClient/GeneratedArtifacts 246 | **/*.DesktopClient/ModelManifest.xml 247 | **/*.Server/GeneratedArtifacts 248 | **/*.Server/ModelManifest.xml 249 | _Pvt_Extensions 250 | 251 | # Paket dependency manager 252 | .paket/paket.exe 253 | paket-files/ 254 | 255 | # FAKE - F# Make 256 | .fake/ 257 | 258 | # JetBrains Rider 259 | .idea/ 260 | *.sln.iml 261 | .vs/ 262 | 263 | # CodeRush 264 | .cr/ 265 | 266 | # Python Tools for Visual Studio (PTVS) 267 | __pycache__/ 268 | *.pyc 269 | 270 | # Cake - Uncomment if you are using it 271 | # tools/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jesse Emerick + 4 | Copyright (c) 2018 Robin Michael (siliconrob) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /NGeoHash.Tests.Unit/GeoHashTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace NGeoHash.Tests.Unit 5 | { 6 | public class GeoHashTests 7 | { 8 | private const double Latitude = 37.8324; 9 | private const double Longitude = 112.5584; 10 | 11 | private const string HashString = "ww8p1r4t8"; 12 | private const long HashStringInt = 4064984913515641; 13 | 14 | [Fact] 15 | public void DecodeInvalid() 16 | { 17 | Assert.ThrowsAny(() => GeoHash.Decode(@"happy times")); 18 | } 19 | 20 | [Fact] 21 | public void Encode_WhenTheNumberOfCharsIsNotProvided_ReturnsHashStringWithTheDefaultNumberOfChars() 22 | { 23 | const int defaultHashStringNumberOfChars = 9; 24 | 25 | var actualHashString = GeoHash.Encode(Latitude, Longitude); 26 | 27 | Assert.Equal(defaultHashStringNumberOfChars, actualHashString.Length); 28 | Assert.Equal(HashString, actualHashString); 29 | } 30 | 31 | [Fact] 32 | public void Encode_WhenTheNumberOfCharsIsProvided_ReturnsHashStringWithTheProvidedNumberOfChars() 33 | { 34 | const int hashStringNumberOfChars = 3; 35 | 36 | var actualHashString = GeoHash.Encode(32, 117, hashStringNumberOfChars); 37 | 38 | Assert.Equal(hashStringNumberOfChars, actualHashString.Length); 39 | Assert.Equal("wte", actualHashString); 40 | } 41 | 42 | [Fact] 43 | public void EncodeInt_WhenTheBitDepthIsNotProvided_ReturnsHashStringIntWithTheDefaultBitDepth() 44 | { 45 | var actualHashStringInt = GeoHash.EncodeInt(Latitude, Longitude); 46 | 47 | Assert.Equal(HashStringInt, actualHashStringInt); 48 | } 49 | 50 | [Fact] 51 | public void EncodeInt_WhenTheBitDepthIsProvided_ReturnsHashStringIntWithTheProvidedBitDepth() 52 | { 53 | var actualHashStringInt = GeoHash.EncodeInt(Latitude, Longitude, 26); 54 | 55 | Assert.Equal(60572995, actualHashStringInt); 56 | } 57 | 58 | [Fact] 59 | public void Decode_WhenHashStringHasDefaultLength_ReturnsCoordinatesWithinStandardPrecision() 60 | { 61 | var geoHashDecodeResult = GeoHash.Decode(HashString); 62 | 63 | Assert.True(Math.Abs(Latitude - geoHashDecodeResult.Coordinates.Lat) < 0.0001, "(37.8324 - " + geoHashDecodeResult.Coordinates.Lat + " was >= 0.0001"); 64 | Assert.True(Math.Abs(Longitude - geoHashDecodeResult.Coordinates.Lon) < 0.0001, "(112.5584 - " + geoHashDecodeResult.Coordinates.Lon + " was >= 0.0001"); 65 | } 66 | 67 | [Fact] 68 | public void DecodeInt_WhenHashStringIntHasDefaultBitDepth_ReturnsCoordinatesWithinStandardPrecision() 69 | { 70 | var geoHashDecodeResult = GeoHash.DecodeInt(HashStringInt); 71 | 72 | Assert.True(Math.Abs(Latitude - geoHashDecodeResult.Coordinates.Lat) < 0.0001, "(37.8324 - " + geoHashDecodeResult.Coordinates.Lat + " was >= 0.0001"); 73 | Assert.True(Math.Abs(Longitude - geoHashDecodeResult.Coordinates.Lon) < 0.0001, "(112.5584 - " + geoHashDecodeResult.Coordinates.Lon + " was >= 0.0001"); 74 | } 75 | 76 | [Theory] 77 | [InlineData("dqcjq", new[] { 1, 0 }, "dqcjw")] 78 | [InlineData("dqcjq", new[] { -1, -1 }, "dqcjj")] 79 | public void Neighbor_WhenDirectionIsProvided_ReturnsTheNeighborInTheProvidedDirection(string hashString, int[] direction, string expectedNeighborHashString) 80 | { 81 | var neighborHashString = GeoHash.Neighbor(hashString, direction); 82 | 83 | Assert.Equal(expectedNeighborHashString, neighborHashString); 84 | } 85 | 86 | [Theory] 87 | [InlineData(1702789509, new[] { 1, 0 }, 32, 1702789520)] 88 | [InlineData(27898503327470, new[] { -1, -1 }, 46, 27898503327465)] 89 | public void NeighborInt_WhenDirectionIsProvided_ReturnsTheNeighborInTheProvidedDirection(long hashInt, int[] direction, int bitDepth, long expectedNeighborHashInt) 90 | { 91 | var neighborHashInt = GeoHash.NeighborInt(hashInt, direction, bitDepth); 92 | 93 | Assert.Equal(expectedNeighborHashInt, neighborHashInt); 94 | } 95 | 96 | [Fact] 97 | public void Neighbors_ReturnsNeighborsInAllDirections() 98 | { 99 | var expectedNeighbors = new[] { "dqcjw", "dqcjx", "dqcjr", "dqcjp", "dqcjn", "dqcjj", "dqcjm", "dqcjt" }; 100 | 101 | var neighbors = GeoHash.Neighbors("dqcjq"); 102 | 103 | Assert.Equal(expectedNeighbors, neighbors); 104 | } 105 | 106 | [Fact] 107 | public void NeighborsInt_ReturnsNeighborsInAllDirections() 108 | { 109 | var expectedNeighbors = new long[] { 1702789520, 1702789522, 1702789511, 1702789510, 1702789508, 1702789422, 1702789423, 1702789434 }; 110 | 111 | var neighbors = GeoHash.NeighborsInt(1702789509, 32); 112 | 113 | Assert.Equal(expectedNeighbors, neighbors); 114 | } 115 | 116 | [Fact] 117 | public void BBoxes_ReturnsHashStringsBetweenCoordinates() 118 | { 119 | var bBoxes = GeoHash.Bboxes(30, 120, 30.0001, 120.0001, 8); 120 | 121 | Assert.Equal(GeoHash.Encode(30.0001, 120.0001, 8), bBoxes[bBoxes.Length -1]); 122 | } 123 | 124 | [Fact] 125 | public void BboxesInt_ReturnsHashStringsIntBetweenCoordinates() 126 | { 127 | var bBoxes = GeoHash.BboxesInt(30, 120, 30.0001, 120.0001, 8); 128 | 129 | Assert.Equal(GeoHash.EncodeInt(30.0001, 120.0001, 8), bBoxes[bBoxes.Length - 1]); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /NGeoHash.Tests.Unit/NGeoHash.Tests.Unit.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | netcoreapp2.0;netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /NGeoHash.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NGeoHash", "NGeoHash\NGeoHash.csproj", "{EF4DB175-3B80-49BB-AC0B-3DFD65312F7E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NGeoHash.Tests.Unit", "NGeoHash.Tests.Unit\NGeoHash.Tests.Unit.csproj", "{26492204-BB91-419A-ADE4-88AE04B37C98}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {EF4DB175-3B80-49BB-AC0B-3DFD65312F7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {EF4DB175-3B80-49BB-AC0B-3DFD65312F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {EF4DB175-3B80-49BB-AC0B-3DFD65312F7E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {EF4DB175-3B80-49BB-AC0B-3DFD65312F7E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {26492204-BB91-419A-ADE4-88AE04B37C98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {26492204-BB91-419A-ADE4-88AE04B37C98}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {26492204-BB91-419A-ADE4-88AE04B37C98}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {26492204-BB91-419A-ADE4-88AE04B37C98}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {371F7ACA-F23E-4DFE-AAAF-8ABAFF5055E7} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /NGeoHash/BoundingBox.cs: -------------------------------------------------------------------------------- 1 | namespace NGeoHash 2 | { 3 | public class BoundingBox 4 | { 5 | public Coordinates Minimum { get; set; } 6 | public Coordinates Maximum { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /NGeoHash/Coordinates.cs: -------------------------------------------------------------------------------- 1 | namespace NGeoHash 2 | { 3 | public class Coordinates 4 | { 5 | public double Lat { get; set; } 6 | public double Lon { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /NGeoHash/GeoHash.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NGeoHash 6 | { 7 | public static class GeoHash 8 | { 9 | /** 10 | * 11 | * Permission is hereby granted, free of charge, to any person 12 | * obtaining a copy of this software and associated documentation 13 | * files (the "Software"), to deal in the Software without 14 | * restriction, including without limitation the rights to use, copy, 15 | * modify, merge, publish, distribute, sublicense, and/or sell copies 16 | * of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be 20 | * included in all copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 26 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 27 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 28 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | * 31 | */ 32 | private const string Base32Codes = "0123456789bcdefghjkmnpqrstuvwxyz"; 33 | 34 | private static readonly Dictionary Base32CodesDict = 35 | Base32Codes.ToDictionary(chr => chr, chr => Base32Codes.IndexOf(chr)); 36 | 37 | /** 38 | * Encode 39 | * 40 | * Create a Geohash out of a latitude and longitude that is 41 | * `numberOfChars` long. 42 | * 43 | * @param {double} latitude 44 | * @param {double} longitude 45 | * @param {int} numberOfChars 46 | * @returns {string} 47 | */ 48 | public static string Encode(double latitude, double longitude, int numberOfChars = 9) 49 | { 50 | var chars = new List(); 51 | var bits = 0; 52 | var bitsTotal = 0; 53 | var hashValue = 0; 54 | var maxLat = 90D; 55 | var minLat = -90D; 56 | var maxLon = 180D; 57 | var minLon = -180D; 58 | while (chars.Count < numberOfChars) 59 | { 60 | double mid; 61 | if (bitsTotal % 2 == 0) 62 | { 63 | mid = (maxLon + minLon) / 2; 64 | if (longitude > mid) 65 | { 66 | hashValue = (hashValue << 1) + 1; 67 | minLon = mid; 68 | } 69 | else 70 | { 71 | hashValue = (hashValue << 1) + 0; 72 | maxLon = mid; 73 | } 74 | } 75 | else 76 | { 77 | mid = (maxLat + minLat) / 2; 78 | if (latitude > mid) 79 | { 80 | hashValue = (hashValue << 1) + 1; 81 | minLat = mid; 82 | } 83 | else 84 | { 85 | hashValue = (hashValue << 1) + 0; 86 | maxLat = mid; 87 | } 88 | } 89 | 90 | bits++; 91 | bitsTotal++; 92 | if (bits != 5) 93 | { 94 | continue; 95 | } 96 | 97 | var code = Base32Codes[hashValue]; 98 | chars.Add(code); 99 | bits = 0; 100 | hashValue = 0; 101 | } 102 | 103 | return string.Join("", chars.ToArray()); 104 | } 105 | 106 | /** 107 | * Encode Integer 108 | * 109 | * Create a Geohash out of a latitude and longitude that is of 'bitDepth'. 110 | * 111 | * @param {double} latitude 112 | * @param {double} longitude 113 | * @param {int} bitDepth 114 | * @returns {long} 115 | */ 116 | public static long EncodeInt(double latitude, double longitude, int bitDepth = 52) 117 | { 118 | 119 | var bitsTotal = 0; 120 | var maxLat = 90D; 121 | var minLat = -90D; 122 | var maxLon = 180D; 123 | var minLon = -180D; 124 | long combinedBits = 0; 125 | 126 | while (bitsTotal < bitDepth) 127 | { 128 | combinedBits *= 2; 129 | double mid; 130 | if (bitsTotal % 2 == 0) 131 | { 132 | mid = (maxLon + minLon) / 2; 133 | if (longitude > mid) 134 | { 135 | combinedBits += 1; 136 | minLon = mid; 137 | } 138 | else 139 | { 140 | maxLon = mid; 141 | } 142 | } 143 | else 144 | { 145 | mid = (maxLat + minLat) / 2; 146 | if (latitude > mid) 147 | { 148 | combinedBits += 1; 149 | minLat = mid; 150 | } 151 | else 152 | { 153 | maxLat = mid; 154 | } 155 | } 156 | 157 | bitsTotal++; 158 | } 159 | 160 | return combinedBits; 161 | } 162 | 163 | /** 164 | * Decode Bounding Box 165 | * 166 | * Decode hashString into a bound box matches it. Data returned in a four-element array: [minlat, minlon, maxlat, maxlon] 167 | * @param {string} hashString 168 | * @returns {double[]} 169 | */ 170 | public static BoundingBox DecodeBbox(string hashString) 171 | { 172 | 173 | var isLon = true; 174 | var maxLat = 90D; 175 | var minLat = -90D; 176 | var maxLon = 180D; 177 | var minLon = -180D; 178 | 179 | foreach (var code in hashString.ToLower()) 180 | { 181 | //var code = hash_string.ToLower()[i]; 182 | var hashValue = Base32CodesDict[code]; 183 | 184 | for (var bits = 4; bits >= 0; bits--) 185 | { 186 | var bit = (hashValue >> bits) & 1; 187 | double mid; 188 | if (isLon) 189 | { 190 | mid = (maxLon + minLon) / 2; 191 | if (bit == 1) 192 | { 193 | minLon = mid; 194 | } 195 | else 196 | { 197 | maxLon = mid; 198 | } 199 | } 200 | else 201 | { 202 | mid = (maxLat + minLat) / 2; 203 | if (bit == 1) 204 | { 205 | minLat = mid; 206 | } 207 | else 208 | { 209 | maxLat = mid; 210 | } 211 | } 212 | 213 | isLon = !isLon; 214 | } 215 | } 216 | 217 | return new BoundingBox 218 | { 219 | Maximum = new Coordinates 220 | { 221 | Lat = maxLat, 222 | Lon = maxLon 223 | }, 224 | Minimum = new Coordinates 225 | { 226 | Lat = minLat, 227 | Lon = minLon 228 | } 229 | }; 230 | } 231 | 232 | /** 233 | * Decode Bounding Box Integer 234 | * 235 | * Decode hash number into a bound box matches it. Data returned in a four-element array: [minlat, minlon, maxlat, maxlon] 236 | * @param {long} hashInt 237 | * @param {int} bitDepth 238 | * @returns {double} 239 | */ 240 | public static BoundingBox DecodeBboxInt(long hashInt, int bitDepth = 52) 241 | { 242 | 243 | 244 | var maxLat = 90D; 245 | var minLat = -90D; 246 | var maxLon = 180D; 247 | var minLon = -180D; 248 | 249 | var step = bitDepth / 2; 250 | 251 | for (var i = 0; i < step; i++) 252 | { 253 | var lonBit = DecodeBit(hashInt, (step - i) * 2 - 1); 254 | var latBit = DecodeBit(hashInt, (step - i) * 2 - 2); 255 | if (latBit == 0) 256 | { 257 | maxLat = (maxLat + minLat) / 2; 258 | } 259 | else 260 | { 261 | minLat = (maxLat + minLat) / 2; 262 | } 263 | if (lonBit == 0) 264 | { 265 | maxLon = (maxLon + minLon) / 2; 266 | } 267 | else 268 | { 269 | minLon = (maxLon + minLon) / 2; 270 | } 271 | } 272 | return new BoundingBox 273 | { 274 | Maximum = new Coordinates 275 | { 276 | Lat = maxLat, 277 | Lon = maxLon 278 | }, 279 | Minimum = new Coordinates 280 | { 281 | Lat = minLat, 282 | Lon = minLon 283 | } 284 | }; 285 | } 286 | 287 | public static long DecodeBit(long bits, int position) 288 | { 289 | return (long) (bits / Math.Pow(2, position)) & 0x01; 290 | } 291 | 292 | /** 293 | * Decode 294 | * 295 | * Decode a hash string into pair of latitude and longitude. A javascript object is returned with keys `latitude`, 296 | * `longitude` and `error`. 297 | * @param {string} hashString 298 | * @returns {GeohashDecodeResult} 299 | */ 300 | public static GeohashDecodeResult Decode(string hashString) 301 | { 302 | var bbox = DecodeBbox(hashString); 303 | var lat = (bbox.Minimum.Lat + bbox.Maximum.Lat) / 2; 304 | var lon = (bbox.Minimum.Lon + bbox.Maximum.Lon) / 2; 305 | var latErr = bbox.Maximum.Lat - lat; 306 | var lonErr = bbox.Maximum.Lon - lon; 307 | return new GeohashDecodeResult 308 | { 309 | Coordinates = new Coordinates 310 | { 311 | Lat = lat, 312 | Lon = lon 313 | }, 314 | Error = new Coordinates 315 | { 316 | Lat = latErr, 317 | Lon = lonErr 318 | } 319 | 320 | }; 321 | } 322 | 323 | /** 324 | * Decode Integer 325 | * 326 | * Decode a hash number into pair of latitude and longitude. A javascript object is returned with keys `latitude`, 327 | * `longitude` and `error`. 328 | * @param {long} hashInt 329 | * @param {int} bitDepth 330 | * @returns {GeohashDecodeResult} 331 | */ 332 | public static GeohashDecodeResult DecodeInt(long hashInt, int bitDepth = 52) 333 | { 334 | var bbox = DecodeBboxInt(hashInt, bitDepth); 335 | var lat = (bbox.Minimum.Lat + bbox.Maximum.Lat) / 2; 336 | var lon = (bbox.Minimum.Lon + bbox.Maximum.Lon) / 2; 337 | var latErr = bbox.Maximum.Lat - lat; 338 | var lonErr = bbox.Maximum.Lon - lon; 339 | return new GeohashDecodeResult 340 | { 341 | Coordinates = new Coordinates 342 | { 343 | Lat = lat, 344 | Lon = lon 345 | }, 346 | Error = new Coordinates 347 | { 348 | Lat = latErr, 349 | Lon = lonErr 350 | } 351 | }; 352 | } 353 | 354 | /** 355 | * Neighbor 356 | * 357 | * Find neighbor of a geohash string in certain direction. Direction is a two-element array, i.e. [1,0] means north, [-1,-1] means southwest. 358 | * direction [lat, lon], i.e. 359 | * [1,0] - north 360 | * [1,1] - northeast 361 | * ... 362 | * @param {string} hashString 363 | * @param {int[]} Direction as a 2D normalized vector. 364 | * @returns {string} 365 | */ 366 | public static string Neighbor(string hashString, int[] direction) 367 | { 368 | var lonLat = Decode(hashString); 369 | var neighborLat = lonLat.Coordinates.Lat + direction[0] * lonLat.Error.Lat * 2; 370 | var neighborLon = lonLat.Coordinates.Lon + direction[1] * lonLat.Error.Lon * 2; 371 | return Encode(neighborLat, neighborLon, hashString.Count()); 372 | } 373 | 374 | /** 375 | * Neighbor Integer 376 | * 377 | * Find neighbor of a geohash integer in certain direction. Direction is a two-element array, i.e. [1,0] means north, [-1,-1] means southwest. 378 | * direction [lat, lon], i.e. 379 | * [1,0] - north 380 | * [1,1] - northeast 381 | * ... 382 | * @param {long} hash 383 | * @param {int[]} direction 384 | * @param {int} bitdepth 385 | * @returns {long} 386 | */ 387 | public static long NeighborInt(long hashInt, int[] direction, int bitDepth = 52) 388 | { 389 | var lonlat = DecodeInt(hashInt, bitDepth); 390 | var neighborLat = lonlat.Coordinates.Lat + direction[0] * lonlat.Error.Lat * 2; 391 | var neighborLon = lonlat.Coordinates.Lon + direction[1] * lonlat.Error.Lon * 2; 392 | return EncodeInt(neighborLat, neighborLon, bitDepth); 393 | } 394 | 395 | /** 396 | * Neighbors 397 | * 398 | * Returns all neighbors' hashstrings clockwise from north around to northwest 399 | * 7 0 1 400 | * 6 x 2 401 | * 5 4 3 402 | * @param {string} hashString 403 | * @returns {encoded neighborHashList|string[]} 404 | */ 405 | public static string[] Neighbors(string hashString) 406 | { 407 | var hashstringLength = hashString.Count(); 408 | var lonlat = Decode(hashString); 409 | var coords = new GeohashDecodeResult 410 | { 411 | Coordinates = lonlat.Coordinates, 412 | Error = new Coordinates 413 | { 414 | Lat = lonlat.Error.Lat * 2, 415 | Lon = lonlat.Error.Lon * 2 416 | } 417 | }; 418 | 419 | return new[] 420 | { 421 | EncodeNeighbor(hashstringLength, 1, 0, coords), 422 | EncodeNeighbor(hashstringLength, 1, 1, coords), 423 | EncodeNeighbor(hashstringLength, 0, 1, coords), 424 | EncodeNeighbor(hashstringLength, -1, 1, coords), 425 | EncodeNeighbor(hashstringLength, -1, 0, coords), 426 | EncodeNeighbor(hashstringLength, -1, -1, coords), 427 | EncodeNeighbor(hashstringLength, 0, -1, coords), 428 | EncodeNeighbor(hashstringLength, 1, -1, coords) 429 | }; 430 | 431 | 432 | } 433 | 434 | public static string EncodeNeighbor(int hashstringLength, int neighborLatDir, int neighborLonDir, 435 | GeohashDecodeResult coords) 436 | { 437 | var neighborLat = coords.Coordinates.Lat + neighborLatDir * coords.Error.Lat; 438 | var neighborLon = coords.Coordinates.Lon + neighborLonDir * coords.Error.Lon; 439 | return Encode(neighborLat, neighborLon, hashstringLength); 440 | } 441 | 442 | 443 | /** 444 | * Neighbors Integer 445 | * 446 | * Returns all neighbors' hash integers clockwise from north around to northwest 447 | * 7 0 1 448 | * 6 x 2 449 | * 5 4 3 450 | * @param {long} hashInt 451 | * @param {int} bitDepth 452 | * @returns {EncodeInt'd neighborHashIntList|long[]} 453 | */ 454 | public static long[] NeighborsInt(long hashInt, int bitDepth = 52) 455 | { 456 | 457 | var lonlat = DecodeInt(hashInt, bitDepth); 458 | var coords = new GeohashDecodeResult 459 | { 460 | Coordinates = lonlat.Coordinates, 461 | Error = new Coordinates 462 | { 463 | Lat = lonlat.Error.Lat * 2, 464 | Lon = lonlat.Error.Lon * 2 465 | } 466 | }; 467 | 468 | return new[] 469 | { 470 | EncodeNeighborInt(1, 0, coords, bitDepth), 471 | EncodeNeighborInt(1, 1, coords, bitDepth), 472 | EncodeNeighborInt(0, 1, coords, bitDepth), 473 | EncodeNeighborInt(-1, 1, coords, bitDepth), 474 | EncodeNeighborInt(-1, 0, coords, bitDepth), 475 | EncodeNeighborInt(-1, -1, coords, bitDepth), 476 | EncodeNeighborInt(0, -1, coords, bitDepth), 477 | EncodeNeighborInt(1, -1, coords, bitDepth) 478 | }; 479 | 480 | } 481 | 482 | 483 | public static long EncodeNeighborInt(int neighborLatDir, int neighborLonDir, GeohashDecodeResult coords, 484 | int bitDepth) 485 | { 486 | var neighborLat = coords.Coordinates.Lat + neighborLatDir * coords.Error.Lat; 487 | var neighborLon = coords.Coordinates.Lon + neighborLonDir * coords.Error.Lon; 488 | return EncodeInt(neighborLat, neighborLon, bitDepth); 489 | } 490 | 491 | 492 | /** 493 | * Bounding Boxes 494 | * 495 | * Return all the hashString between minLat, minLon, maxLat, maxLon in numberOfChars 496 | * @param {double} minLat 497 | * @param {double} minLon 498 | * @param {double} maxLat 499 | * @param {double} maxLon 500 | * @param {int} numberOfChars 501 | * @returns {bboxes.hashList|string[]} 502 | */ 503 | public static string[] Bboxes(double minLat, double minLon, double maxLat, double maxLon, int numberOfChars = 9) 504 | { 505 | var hashSouthWest = Encode(minLat, minLon, numberOfChars); 506 | var hashNorthEast = Encode(maxLat, maxLon, numberOfChars); 507 | 508 | var latLon = Decode(hashSouthWest); 509 | 510 | var perLat = latLon.Error.Lat * 2; 511 | var perLon = latLon.Error.Lon * 2; 512 | 513 | var boxSouthWest = DecodeBbox(hashSouthWest); 514 | var boxNorthEast = DecodeBbox(hashNorthEast); 515 | 516 | var latStep = Math.Round((boxNorthEast.Minimum.Lat - boxSouthWest.Minimum.Lat) / perLat); 517 | var lonStep = Math.Round((boxNorthEast.Minimum.Lon - boxSouthWest.Minimum.Lon) / perLon); 518 | 519 | var hashList = new List(); 520 | 521 | for (var lat = 0; lat <= latStep; lat++) 522 | { 523 | for (var lon = 0; lon <= lonStep; lon++) 524 | { 525 | hashList.Add(Neighbor(hashSouthWest, new[] {lat, lon})); 526 | } 527 | } 528 | return hashList.ToArray(); 529 | } 530 | 531 | /** 532 | * Bounding Boxes Integer 533 | * 534 | * Return all the hash integers between minLat, minLon, maxLat, maxLon in bitDepth 535 | * @param {double} minLat 536 | * @param {double} minLon 537 | * @param {double} maxLat 538 | * @param {double} maxLon 539 | * @param {int} bitDepth 540 | * @returns {bboxes.hashList|long[]} 541 | */ 542 | 543 | public static long[] BboxesInt(double minLat, double minLon, double maxLat, double maxLon, int bitDepth = 52) 544 | { 545 | 546 | 547 | var hashSouthWest = EncodeInt(minLat, minLon, bitDepth); 548 | var hashNorthEast = EncodeInt(maxLat, maxLon, bitDepth); 549 | 550 | var latlon = DecodeInt(hashSouthWest, bitDepth); 551 | 552 | var perLat = latlon.Error.Lat * 2; 553 | var perLon = latlon.Error.Lon * 2; 554 | 555 | var boxSouthWest = DecodeBboxInt(hashSouthWest, bitDepth); 556 | var boxNorthEast = DecodeBboxInt(hashNorthEast, bitDepth); 557 | 558 | var latStep = Math.Round((boxNorthEast.Minimum.Lat - boxSouthWest.Minimum.Lat) / perLat); 559 | var lonStep = Math.Round((boxNorthEast.Minimum.Lon - boxSouthWest.Minimum.Lon) / perLon); 560 | 561 | var hashList = new List(); 562 | 563 | for (var lat = 0; lat <= latStep; lat++) 564 | { 565 | for (var lon = 0; lon <= lonStep; lon++) 566 | { 567 | hashList.Add(NeighborInt(hashSouthWest, new[] {lat, lon}, bitDepth)); 568 | } 569 | } 570 | return hashList.ToArray(); 571 | } 572 | } 573 | } 574 | -------------------------------------------------------------------------------- /NGeoHash/GeohashDecodeResult.cs: -------------------------------------------------------------------------------- 1 | namespace NGeoHash 2 | { 3 | public class GeohashDecodeResult 4 | { 5 | public Coordinates Coordinates { get; set; } 6 | public Coordinates Error { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /NGeoHash/NGeoHash.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 1.2.0 6 | https://github.com/jesseemerick/NGeoHash 7 | Jesse Emerick, Leonardo Rosales, Robin Michael (siliconrob) 8 | C# Port of Node-Geohash. This is a line for line port to C# of Node-Geohash https://github.com/sunng87/node-geohash 9 | https://github.com/jesseemerick/NGeoHash/blob/master/LICENSE 10 | Copyright (c) 2014 Jesse Emerick + 11 | Copyright (c) 2017 Leonardo Rosales 12 | Copyright (c) 2018 Robin Michael (siliconrob) 13 | Added a BoundingBox type 14 | geohash node-geohash ngeohash 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### NGeoHash 2 | ======== 3 | 4 | [![NuGet Version and Downloads count](https://buildstats.info/nuget/ngeohash)](https://www.nuget.org/packages/ngeohash) 5 | 6 | C# Port of Node-Geohash 7 | 8 | This is a line for line port to C# of Node-Geohash https://github.com/sunng87/node-geohash 9 | 10 | The project is available in nuget - https://www.nuget.org/packages/ngeohash/ 11 | 12 | #### Usage 13 | ```csharp 14 | 15 | // Fort Worth 16 | var location = new 17 | { 18 | latitude = 32.768799, 19 | longitude = -97.309341, 20 | }; 21 | var encoded = GeoHash.Encode(location.latitude, location.longitude); // "9vff3tms0" 22 | 23 | var decoded = GeoHash.Decode("9vff3tms0"); 24 | var latitude = decoded.Coordinates.Lat; // 32.768805027008057 25 | var longitude = decoded.Coordinates.Lon; // -97.309319972991943 26 | 27 | var errorMarginLat = decoded.Error.Lat // 2.1457672119140625E-05 28 | var errorMarginLon = decoded.Error.Lon // 2.1457672119140625E-05 29 | 30 | ``` 31 | 32 | 33 | #### Invalid Usage 34 | ```csharp 35 | 36 | var bad = GeoHash.Decode("happy times"); // Throws exception 37 | ``` 38 | --------------------------------------------------------------------------------