Created
February 22, 2019 20:41
-
-
Save corstian/8ac817cc378c56de69b43aff8cf398f2 to your computer and use it in GitHub Desktop.
Representing coordinates in a human readable way
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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