Representing coordinates in a human readable way
<Query Kind="Program"> | |
<Reference><RuntimeDirectory>\System.IO.Compression.dll</Reference> | |
<Reference><RuntimeDirectory>\System.IO.Compression.FileSystem.dll</Reference> | |
<NuGetReference>Humanizer</NuGetReference> | |
<NuGetReference>NetTopologySuite</NuGetReference> | |
<Namespace>System.IO.Compression</Namespace> | |
<Namespace>System.Net</Namespace> | |
<Namespace>System.Threading.Tasks</Namespace> | |
<Namespace>NetTopologySuite.Index.KdTree</Namespace> | |
<Namespace>GeoAPI.Geometries</Namespace> | |
<Namespace>NetTopologySuite.Geometries</Namespace> | |
<Namespace>Humanizer</Namespace> | |
<Namespace>System.Globalization</Namespace> | |
</Query> | |
//Coordinate coordinate = new Coordinate(51.455230, 4.374745); | |
//Coordinate coordinate = new Coordinate(51.465941, 4.291208); | |
//Coordinate coordinate = new Coordinate(49.988079, 3.382322); | |
//Coordinate coordinate = new Coordinate(49.823420, 2.186107); | |
//Coordinate coordinate = new Coordinate(51.418730, 4.294168); | |
Coordinate coordinate = new Coordinate(51.489144, 4.153076); | |
//Coordinate coordinate = new Coordinate(51.528751, 4.428284); | |
async void Main() | |
{ | |
// We're having trouble caching the whole tree. Rather have the string cached then. | |
var file = await Util.Cache(async () => await GeoNames.DownloadFile(), "cities5000.zip"); | |
var tree = new KdTree<LocationEntry>(); | |
GeoNames.ExtractData(file) | |
.ToList() | |
.ForEach((i) => tree.Insert(new Coordinate(i.Latitude, i.Longitude), i)); | |
var landmark = tree.NearestNeighbor(coordinate); | |
// We get the bearing to the city (angle of the line between two points) | |
var bearing = Geo.DegreeBearing(landmark.Coordinate, coordinate); | |
var distance = (int)(landmark.Coordinate.DistanceTo(coordinate) / 1000); | |
$"{distance}km {bearing.ToHeading(HeadingStyle.Full)} of {landmark.Data.Name}".Dump(); | |
} | |
// Define other methods and classes here | |
public static class GeoNames | |
{ | |
public static async Task<string> DownloadFile() | |
{ | |
using (var client = new WebClient()) | |
{ | |
var location = Path.GetTempFileName(); | |
await client.DownloadFileTaskAsync("http://download.geonames.org/export/dump/cities5000.zip", location); | |
var entry = ZipFile.OpenRead(location).Entries[0].Open(); | |
var reader = new StreamReader(entry); | |
var data = await reader.ReadToEndAsync(); | |
return data; | |
} | |
} | |
public static IEnumerable<LocationEntry> ExtractData(string data) | |
{ | |
foreach (var line in data.Split('\n', '\r')) | |
{ | |
var tabs = line.Split('\t'); | |
if (tabs.Length != 19) continue; | |
yield return new LocationEntry | |
{ | |
Name = tabs[1], | |
Latitude = Convert.ToDouble(tabs[4], CultureInfo.InvariantCulture), | |
Longitude = Convert.ToDouble(tabs[5], CultureInfo.InvariantCulture) | |
}; | |
} | |
} | |
} | |
/* | |
* So I have pasted my own geo helpers here because I'm too lazy to figure out if NTS has these functions, too. | |
* | |
* As I'm obviously not smart enough to come up with this stuff myself, and I have pulled these methods from all | |
* over the web, feel free to steal. | |
*/ | |
public static class Geo | |
{ | |
// See https://stackoverflow.com/a/2042883/1720761 for more information about these methods. | |
public static double DegreeBearing( | |
double lat1, double lon1, | |
double lat2, double lon2) | |
{ | |
var dLon = ToRad(lon2 - lon1); | |
var dPhi = Math.Log( | |
Math.Tan(ToRad(lat2) / 2 + Math.PI / 4) / Math.Tan(ToRad(lat1) / 2 + Math.PI / 4)); | |
if (Math.Abs(dLon) > Math.PI) | |
dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); | |
return ToBearing(Math.Atan2(dLon, dPhi)); | |
} | |
public static double DegreeBearing( | |
IPoint p1, | |
IPoint p2) | |
{ | |
return DegreeBearing(p1.X, p1.Y, p2.X, p2.Y); | |
} | |
public static double DegreeBearing( | |
Coordinate c1, | |
Coordinate c2) | |
{ | |
return DegreeBearing(c1.X, c1.Y, c2.X, c2.Y); | |
} | |
public static double ToRad(this double degrees) | |
{ | |
return degrees * (Math.PI / 180); | |
} | |
public static double ToDegrees(this double radians) | |
{ | |
return radians * 180 / Math.PI; | |
} | |
public static double ToBearing(this double radians) | |
{ | |
// convert radians to degrees (as bearing: 0...360) | |
return (ToDegrees(radians) + 360) % 360; | |
} | |
// We're getting a rhumb line again | |
public static double DistanceTo(double lat1, double long1, double lat2, double long2) | |
{ | |
if (double.IsNaN(lat1) || double.IsNaN(long1) || double.IsNaN(lat2) || | |
double.IsNaN(long2)) | |
{ | |
throw new ArgumentException("Argument latitude or longitude is not a number"); | |
} | |
var d1 = lat1 * (Math.PI / 180.0); | |
var num1 = long1 * (Math.PI / 180.0); | |
var d2 = lat2 * (Math.PI / 180.0); | |
var num2 = long2 * (Math.PI / 180.0) - num1; | |
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + | |
Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0); | |
return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3))); | |
} | |
public static double DistanceTo(this IPoint from, IPoint to) | |
{ | |
if (double.IsNaN(from.X) || double.IsNaN(from.Y) || double.IsNaN(to.X) || | |
double.IsNaN(to.Y)) | |
{ | |
throw new ArgumentException("Argument latitude or longitude is not a number"); | |
} | |
var d1 = from.X * (Math.PI / 180.0); | |
var num1 = from.Y * (Math.PI / 180.0); | |
var d2 = to.X * (Math.PI / 180.0); | |
var num2 = to.Y * (Math.PI / 180.0) - num1; | |
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + | |
Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0); | |
return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3))); | |
} | |
public static double DistanceTo(this Coordinate from, Coordinate to) { | |
return new Point(from.X, from.Y).DistanceTo(new Point(to.X, to.Y)); | |
} | |
} | |
public class LocationEntry | |
{ | |
public string Name { get; set; } | |
public double Latitude { get; set; } | |
public double Longitude { get; set; } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment