// Copyright (c) 2011, Yves Goergen, http://unclassified.software/source/maidenheadlocator
//
// Copying and distribution of this file, with or without modification, are permitted provided the
// copyright notice and this notice are preserved. This file is offered as-is, without any warranty.
// This class is based on a Perl module by Dirk Koopman, G1TLH, from 2002-11-07.
// Source: http://www.koders.com/perl/fidDAB6FD208AC4F5C0306CA344485FD0899BD2F328.aspx
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Unclassified.Util
{
///
/// Class providing static methods for calculating with Maidenhead locators, especially
/// distance and bearing.
///
public class MaidenheadLocator
{
///
/// Convert a locator to latitude and longitude in degrees
///
/// Locator string to convert
/// LatLng structure
public static LatLng LocatorToLatLng(string locator)
{
locator = locator.Trim().ToUpper();
if (Regex.IsMatch(locator, "^[A-R]{2}[0-9]{2}$"))
{
LatLng ll = new LatLng();
ll.Long = (locator[0] - 'A') * 20 + (locator[2] - '0' + 0.5) * 2 - 180;
ll.Lat = (locator[1] - 'A') * 10 + (locator[3] - '0' + 0.5) - 90;
return ll;
}
else if (Regex.IsMatch(locator, "^[A-R]{2}[0-9]{2}[A-X]{2}$"))
{
LatLng ll = new LatLng();
ll.Long = (locator[0] - 'A') * 20 + (locator[2] - '0') * 2 + (locator[4] - 'A' + 0.5) / 12 - 180;
ll.Lat = (locator[1] - 'A') * 10 + (locator[3] - '0') + (locator[5] - 'A' + 0.5) / 24 - 90;
return ll;
}
else if (Regex.IsMatch(locator, "^[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}$"))
{
LatLng ll = new LatLng();
ll.Long = (locator[0] - 'A') * 20 + (locator[2] - '0') * 2 + (locator[4] - 'A' + 0.0) / 12 + (locator[6] - '0' + 0.5) / 120 - 180;
ll.Lat = (locator[1] - 'A') * 10 + (locator[3] - '0') + (locator[5] - 'A' + 0.0) / 24 + (locator[7] - '0' + 0.5) / 240 - 90;
return ll;
}
else if (Regex.IsMatch(locator, "^[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}$"))
{
LatLng ll = new LatLng();
ll.Long = (locator[0] - 'A') * 20 + (locator[2] - '0') * 2 + (locator[4] - 'A' + 0.0) / 12 + (locator[6] - '0' + 0.0) / 120 + (locator[8] - 'A' + 0.5) / 120 / 24 - 180;
ll.Lat = (locator[1] - 'A') * 10 + (locator[3] - '0') + (locator[5] - 'A' + 0.0) / 24 + (locator[7] - '0' + 0.0) / 240 + (locator[9] - 'A' + 0.5) / 240 / 24 - 90;
return ll;
}
else
{
throw new FormatException("Invalid locator format");
}
}
///
/// Convert latitude and longitude in degrees to a locator
///
/// LatLng structure to convert
/// Locator string
public static string LatLngToLocator(LatLng ll)
{
return LatLngToLocator(ll.Lat, ll.Long, 0);
}
///
/// Convert latitude and longitude in degrees to a locator
///
/// LatLng structure to convert
/// Extra precision (0, 1, 2)
/// Locator string
public static string LatLngToLocator(LatLng ll, int Ext)
{
return LatLngToLocator(ll.Lat, ll.Long, Ext);
}
///
/// Convert latitude and longitude in degrees to a locator
///
/// Latitude to convert
/// Longitude to convert
/// Locator string
public static string LatLngToLocator(double Lat, double Long)
{
return LatLngToLocator(Lat, Long, 0);
}
///
/// Convert latitude and longitude in degrees to a locator
///
/// Latitude to convert
/// Longitude to convert
/// Extra precision (0, 1, 2)
/// Locator string
public static string LatLngToLocator(double Lat, double Long, int Ext)
{
int v;
string locator = "";
Lat += 90;
Long += 180;
locator += (char) ('A' + Math.Floor(Long / 20));
locator += (char) ('A' + Math.Floor(Lat / 10));
Long = Math.IEEERemainder(Long, 20);
if (Long < 0) Long += 20;
Lat = Math.IEEERemainder(Lat, 10);
if (Lat < 0) Lat += 10;
locator += (char) ('0' + Math.Floor(Long / 2));
locator += (char) ('0' + Math.Floor(Lat / 1));
Long = Math.IEEERemainder(Long, 2);
if (Long < 0) Long += 2;
Lat = Math.IEEERemainder(Lat, 1);
if (Lat < 0) Lat += 1;
locator += (char) ('A' + Math.Floor(Long * 12));
locator += (char) ('A' + Math.Floor(Lat * 24));
Long = Math.IEEERemainder(Long, (double) 1 / 12);
if (Long < 0) Long += (double) 1 / 12;
Lat = Math.IEEERemainder(Lat, (double) 1 / 24);
if (Lat < 0) Lat += (double) 1 / 24;
if (Ext >= 1)
{
locator += (char) ('0' + Math.Floor(Long * 120));
locator += (char) ('0' + Math.Floor(Lat * 240));
Long = Math.IEEERemainder(Long, (double) 1 / 120);
if (Long < 0) Long += (double) 1 / 120;
Lat = Math.IEEERemainder(Lat, (double) 1 / 240);
if (Lat < 0) Lat += (double) 1 / 240;
}
if (Ext >= 2)
{
locator += (char) ('A' + Math.Floor(Long * 120 * 24));
locator += (char) ('A' + Math.Floor(Lat * 240 * 24));
Long = Math.IEEERemainder(Long, (double) 1 / 120 / 24);
if (Long < 0) Long += (double) 1 / 120 / 24;
Lat = Math.IEEERemainder(Lat, (double) 1 / 240 / 24);
if (Lat < 0) Lat += (double) 1 / 240 / 24;
}
return locator;
//Lat += 90;
//Long += 180;
//v = (int) (Long / 20);
//Long -= v * 20;
//locator += (char) ('A' + v);
//v = (int) (Lat / 10);
//Lat -= v * 10;
//locator += (char) ('A' + v);
//locator += ((int) (Long / 2)).ToString();
//locator += ((int) Lat).ToString();
//Long -= (int) (Long / 2) * 2;
//Lat -= (int) Lat;
//locator += (char) ('A' + Long * 12);
//locator += (char) ('A' + Lat * 24);
//return locator;
}
///
/// Convert radians to degrees
///
///
///
public static double RadToDeg(double rad)
{
return rad / Math.PI * 180;
}
///
/// Convert degrees to radians
///
///
///
public static double DegToRad(double deg)
{
return deg / 180 * Math.PI;
}
///
/// Calculate the distance in km between two locators
///
/// Start locator string
/// End locator string
/// Distance in km
public static double Distance(string A, string B)
{
return Distance(LocatorToLatLng(A), LocatorToLatLng(B));
}
///
/// Calculate the distance in km between two locators
///
/// Start LatLng structure
/// End LatLng structure
/// Distance in km
public static double Distance(LatLng A, LatLng B)
{
if (A.CompareTo(B) == 0) return 0;
double hn = DegToRad(A.Lat);
double he = DegToRad(A.Long);
double n = DegToRad(B.Lat);
double e = DegToRad(B.Long);
double co = Math.Cos(he - e) * Math.Cos(hn) * Math.Cos(n) + Math.Sin(hn) * Math.Sin(n);
double ca = Math.Atan(Math.Abs(Math.Sqrt(1 - co * co) / co));
if (co < 0) ca = Math.PI - ca;
double dx = 6367 * ca;
return dx;
}
///
/// Calculate the azimuth in degrees between two locators
///
/// Start locator string
/// End locator string
/// Azimuth in degrees
public static double Azimuth(string A, string B)
{
return Azimuth(LocatorToLatLng(A), LocatorToLatLng(B));
}
///
/// Calculate the azimuth in degrees between two locators
///
/// Start LatLng structure
/// End LatLng structure
/// Azimuth in degrees
public static double Azimuth(LatLng A, LatLng B)
{
if (A.CompareTo(B) == 0) return 0;
double hn = DegToRad(A.Lat);
double he = DegToRad(A.Long);
double n = DegToRad(B.Lat);
double e = DegToRad(B.Long);
double co = Math.Cos(he - e) * Math.Cos(hn) * Math.Cos(n) + Math.Sin(hn) * Math.Sin(n);
double ca = Math.Atan(Math.Abs(Math.Sqrt(1 - co * co) / co));
if (co < 0) ca = Math.PI - ca;
double si = Math.Sin(e - he) * Math.Cos(n) * Math.Cos(hn);
co = Math.Sin(n) - Math.Sin(hn) * Math.Cos(ca);
double az = Math.Atan(Math.Abs(si / co));
if (co < 0) az = Math.PI - az;
if (si < 0) az = -az;
if (az < 0) az = az + 2 * Math.PI;
return RadToDeg(az);
}
}
///
/// Simple structure to store a position in latitude and longitude
///
public struct LatLng : IComparable
{
///
/// Latitude, -90 to +90 (N/S direction)
///
public double Lat;
///
/// Longitude, -180 to +180 (W/E direction)
///
public double Long;
public override string ToString()
{
return Long.ToString("#.###") + (Long >= 0 ? "N" : "S") + " " + Lat.ToString("#.###") + (Lat >= 0 ? "E" : "W");
}
public int CompareTo(object to)
{
if (to is LatLng)
{
if (Lat == ((LatLng) to).Lat && Long == ((LatLng) to).Long) return 0;
return -1;
}
return -1;
}
}
}