/
Geohash.cs
207 lines (185 loc) · 6.34 KB
/
Geohash.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
* Geohashing, a Windows Phone 8 app for geohashing.
* Copyright (C) 2013 Lucas Werkmeister
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Microsoft.Phone.Maps.Controls;
using System;
using System.Collections.Generic;
using System.Device.Location;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Devices.Geolocation;
namespace Geohashing
{
public class Geohash
{
private static readonly DateTime Rule30WValidityStart = new DateTime(2008, 05, 27);
private readonly DateTime date;
private readonly GeoCoordinate position;
public GeoCoordinate Position
{
get
{
return position;
}
}
public LocationRectangle Graticule
{
get
{
int top = (int)Math.Ceiling(position.Latitude);
int left = (int)Math.Floor(position.Longitude);
return new LocationRectangle(
north: top,
west: left,
south: top - 1,
east: left + 1);
}
}
public bool Rule30WApplies
{
get
{
return date.CompareTo(Rule30WValidityStart) >= 0 && position.Longitude > -30;
}
}
public DateTime Date
{
get
{
return date.Date;
}
}
private Geohash(DateTime date, GeoCoordinate position)
{
this.date = date;
this.position = position;
}
public override bool Equals(object obj)
{
if (!(obj is Geohash))
return false;
Geohash other = (Geohash)obj;
return other.Position.Equals(Position) && other.Date.Equals(Date);
}
public override int GetHashCode()
{
return Position.GetHashCode() ^ Date.GetHashCode();
}
public static async Task<Geohash> Get()
{
return await Get(DateTime.Now);
}
public static async Task<Geohash> Get(GeoCoordinate position)
{
return await Get(position, DateTime.Now);
}
public static async Task<Geohash> Get(DateTime date)
{
Geolocator geolocator = new Geolocator();
geolocator.DesiredAccuracyInMeters = 50;
return await Get((await geolocator.GetGeopositionAsync()).Coordinate.Convert(), date);
}
public static async Task<Geohash> Get(GeoCoordinate position, DateTime date, GeohashMode geohashMode = GeohashMode.CurrentGraticule)
{
int[] deltas = geohashMode == GeohashMode.CurrentGraticule ? new[] { 0 } : new[] { -1, 0, 1 };
GeoCoordinate nearestHash = position; // Will get overwritten anyways,
double distance = Double.MaxValue; // because of this
foreach (int dx in deltas)
foreach (int dy in deltas)
{
GeoCoordinate newCoordinate = new GeoCoordinate(position.Latitude - dx, position.Longitude - dy);
string[] appendices = calculateAppendices(date, await Djia.Get(convertDate30W(date, newCoordinate)));
string latStr = (int)newCoordinate.Latitude + "." + appendices[0];
string lonStr = (int)newCoordinate.Longitude + "." + appendices[1];
double latitude = Convert.ToDouble(latStr, CultureInfo.InvariantCulture);
double longitude = Convert.ToDouble(lonStr, CultureInfo.InvariantCulture);
GeoCoordinate newHash = new GeoCoordinate(latitude, longitude);
double newDistance = position.GetDistanceTo(newHash);
if (newDistance < distance)
{
nearestHash = newHash;
distance = newDistance;
}
}
return new Geohash(date, nearestHash);
}
private static DateTime convertDate30W(DateTime date, GeoCoordinate position)
{
return position.Longitude > -30 && date.CompareTo(Rule30WValidityStart) >= 0 ? date.Subtract(TimeSpan.FromDays(1)) : date;
}
private static string[] calculateAppendices(DateTime date, string djia)
{
string dateString = date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
string fullString = dateString + '-' + djia;
byte[] result = MD5.Digest(fullString);
ulong part1 = ((ulong)result[0x0] << 0x38)
+ ((ulong)result[0x1] << 0x30)
+ ((ulong)result[0x2] << 0x28)
+ ((ulong)result[0x3] << 0x20)
+ ((ulong)result[0x4] << 0x18)
+ ((ulong)result[0x5] << 0x10)
+ ((ulong)result[0x6] << 0x08)
+ ((ulong)result[0x7] << 0x00);
ulong part2 = ((ulong)result[0x8] << 0x38)
+ ((ulong)result[0x9] << 0x30)
+ ((ulong)result[0xA] << 0x28)
+ ((ulong)result[0xB] << 0x20)
+ ((ulong)result[0xC] << 0x18)
+ ((ulong)result[0xD] << 0x10)
+ ((ulong)result[0xE] << 0x08)
+ ((ulong)result[0xF] << 0x00);
string appendix1 = ((part1 / 2.0) / (long.MaxValue + (ulong)1)).ToString(CultureInfo.InvariantCulture).Substring("0.".Length); // Some tricks are required to divide by ulong.MaxValue + 1
string appendix2 = ((part2 / 2.0) / (long.MaxValue + (ulong)1)).ToString(CultureInfo.InvariantCulture).Substring("0.".Length);
return new[] { appendix1, appendix2 };
}
}
public static class Extensions
{
// From http://matthiasshapiro.com/2012/12/10/window-8-win-phone-code-sharing-httpwebrequest-getresponseasync/
public static Task<HttpWebResponse> GetResponseAsync(this WebRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
var taskComplete = new TaskCompletionSource<HttpWebResponse>();
request.BeginGetResponse(asyncResponse =>
{
try
{
HttpWebRequest responseRequest = (HttpWebRequest)asyncResponse.AsyncState;
HttpWebResponse someResponse = (HttpWebResponse)responseRequest.EndGetResponse(asyncResponse);
taskComplete.TrySetResult(someResponse);
}
catch (WebException webExc)
{
HttpWebResponse failedResponse = (HttpWebResponse)webExc.Response;
taskComplete.TrySetResult(failedResponse);
}
}, request);
return taskComplete.Task;
}
public static GeoCoordinate Convert(this Geocoordinate coordinates)
{
if (coordinates == null)
throw new ArgumentNullException("coordinates");
return new GeoCoordinate(coordinates.Latitude, coordinates.Longitude);
}
}
}