I see that many contests, awards, and other items, use Grid Squares as a means to identify where one is. How can one figure out what one's grid square is, given lat/long?
-
3$\begingroup$ Do you seek an algorithm, or an application? $\endgroup$– VU2NHWOct 23, 2013 at 12:32
-
1$\begingroup$ An algorithm really, but application could be useful to others out there. $\endgroup$– PearsonArtPhotoOct 23, 2013 at 12:43
-
$\begingroup$ Hello @Gabriele, as someone told you before, an answer is not the place to ask a question. If you ask a new question, we'll try to help you. That would be a good way to proceed if you just need a 10-digit Maidenhead calculator. If you want to understand why your code doesn't work, then I'd recommend asking a new question on stackoverflow.com instead. Seriously, we want to help, but we ask that you work with the rules here. $\endgroup$– rclocher3Feb 22, 2022 at 15:56
13 Answers
First of all, if you don't want to do any math, then check out a grid square map, such as this one.
There is an excellent process at this page, and additional resources from ARRL. Essentially, Grid Squares contain 3 pairs, the first and last letters, and the middle numbers. Longitude is always the first, followed by latitude, for each pair. For simplicity, let's assume that West and South are negative lat/long, as is a common convention. For example purposes, I'm going to use 32.123 W, 14.321 N. The key thing is to do the following.:
Longitude
- Add 180 to the longitude, and take the integer value /20, and add one. Then figure out which letter of the alphabet that corresponds to, usually written in upper case. The example will be 147.877/20=7. Adding one will give the 8th letter of the alphabet, or H. Note 7.877 is remaining.
- Take the remainder of what is left, and divide by 2, rounding down. This is the number, no conversion required. The example will give a value of 3. Note 1.877 is remaining.
- Take the remainder that is left, and multiply by 12, and add one. Round down to the nearest integer.. This is the letter of the alphabet, usually written in lower case. The example gives a value of 22+1=23. This will be the letter w.
Latitude
- Add 90 to the longitude, and take the integer value /10, and add one. Then figure out which letter of the alphabet that corresponds to, usually written in upper case. The example will be 104.321/10=10. Adding one will give the 11th letter of the alphabet, or K. Note 4.321 is remaining.
- Take the remainder of what is left, and round down. This is the number, no conversion required. The example will give a value of 4. Note 0.321 is remaining.
- Take the remainder that is left, and multiply by 24, and add one. Round down to the nearest integer.. This is the letter of the alphabet, usually written in lower case. The example gives a value of 7+1=8. This will be the letter h.
Putting them together by pairs, and alternating first longitude then latitude, gives the grid square for 32.123 W, 14.321 N to be HK34wh.
If you'd like to do it yourself, you can use this Python program I wrote.
# -*- coding: utf-8 -*-
import sys
# Convert latitude and longitude to Maidenhead grid locators.
#
# Arguments are in signed decimal latitude and longitude. For example,
# the location of my QTH Palo Alto, CA is: 37.429167, -122.138056 or
# in degrees, minutes, and seconds: 37° 24' 49" N 122° 6' 26" W
upper = 'ABCDEFGHIJKLMNOPQRSTUVWX'
lower = 'abcdefghijklmnopqrstuvwx'
def to_grid(dec_lat, dec_lon):
if not (-180<=dec_lon<180):
sys.stderr.write('longitude must be -180<=lon<180, given %f\n'%dec_lon)
sys.exit(32)
if not (-90<=dec_lat<90):
sys.stderr.write('latitude must be -90<=lat<90, given %f\n'%dec_lat)
sys.exit(33) # can't handle north pole, sorry, [A-R]
adj_lat = dec_lat + 90.0
adj_lon = dec_lon + 180.0
grid_lat_sq = upper[int(adj_lat/10)];
grid_lon_sq = upper[int(adj_lon/20)];
grid_lat_field = str(int(adj_lat%10))
grid_lon_field = str(int((adj_lon/2)%10))
adj_lat_remainder = (adj_lat - int(adj_lat)) * 60
adj_lon_remainder = ((adj_lon) - int(adj_lon/2)*2) * 60
grid_lat_subsq = lower[int(adj_lat_remainder/2.5)]
grid_lon_subsq = lower[int(adj_lon_remainder/5)]
return grid_lon_sq + grid_lat_sq + grid_lon_field + grid_lat_field + grid_lon_subsq + grid_lat_subsq
def usage():
print 'This script takes two arguments, decimal latitude and longitude.'
print 'Example for Newington, Connecticut (W1AW):'
print 'python maidenhead.py 41.714775 -72.727260'
print 'returns: FN31pr'
def test():
# First four test examples are from "Conversion Between Geodetic and Grid Locator Systems",
# by Edmund T. Tyson N5JTY QST January 1989
test_data = (
('Munich', (48.14666,11.60833), 'JN58td'),
('Montevideo', (-34.91,-56.21166), 'GF15vc'),
('Washington, DC', (38.92,-77.065), 'FM18lw'),
('Wellington', (-41.28333,174.745), 'RE78ir'),
('Newington, CT (W1AW)', (41.714775,-72.727260), 'FN31pr'),
('Palo Alto (K6WRU)', (37.413708,-122.1073236), 'CM87wj'),
)
print 'Running self test\n'
passed = True
for name,latlon,grid in test_data:
print 'Testing %s at %f %f:'%(name,latlon[0],latlon[1])
test_grid = to_grid(latlon[0], latlon[1])
if test_grid != grid:
print 'Failed '+test_grid+' should be '+grid
passed = False
else:
print 'Passed '+test_grid
print ''
if passed: print 'Passed!'
else: print 'Failed!'
def main(argv=None):
if argv is None: argv = sys.argv
if len(argv) != 3:
usage()
print ''
test()
else:
print to_grid(float(argv[1]),float(argv[2]))
main()
My first post in SO. Here's a C version... made this for an Arduino project.
void calcLocator(char *dst, double lat, double lon) {
int o1, o2, o3;
int a1, a2, a3;
double remainder;
// longitude
remainder = lon + 180.0;
o1 = (int)(remainder / 20.0);
remainder = remainder - (double)o1 * 20.0;
o2 = (int)(remainder / 2.0);
remainder = remainder - 2.0 * (double)o2;
o3 = (int)(12.0 * remainder);
// latitude
remainder = lat + 90.0;
a1 = (int)(remainder / 10.0);
remainder = remainder - (double)a1 * 10.0;
a2 = (int)(remainder);
remainder = remainder - (double)a2;
a3 = (int)(24.0 * remainder);
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)o3 + 'A';
dst[5] = (char)a3 + 'A';
dst[6] = (char)0;
}
This helped me. For anyone that needs it, here is a port to Java:
public class Location {
String latlon;
String maidenhead;
public Location(String p1, String p2) {
float lat = -100.0f;
float lon = 0.0f;
try {
lat = Float.parseFloat(p1);
lon = Float.parseFloat(p2);
maidenhead = latLonToGridSquare(lat, lon);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Location(float lat, float lon) {
try {
maidenhead = latLonToGridSquare(lat, lon);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void gridSquareToLatLon(String grid) {
}
public String latLonToGridSquare(float lat, float lon) throws Exception{
float adjLat,adjLon;
char GLat,GLon;
String nLat,nLon;
char gLat,gLon;
float rLat,rLon;
String U = "ABCDEFGHIJKLMNOPQRSTUVWX";
String L = U.toLowerCase();
// support Chris Veness 2002-2012 LatLon library and
// other objects with lat/lon properties
// properties could be getter functions, numbers, or strings
if (Float.isNaN(lat)) throw new Exception("lat is NaN");
if (Float.isNaN(lon)) throw new Exception("lon is NaN");
if (Math.abs(lat) == 90.0) throw new Exception("grid squares invalid at N/S poles");
if (Math.abs(lat) > 90) throw new Exception("invalid latitude: "+lat);
if (Math.abs(lon) > 180) throw new Exception("invalid longitude: "+lon);
adjLat = lat + 90;
adjLon = lon + 180;
GLat = U.charAt((int) (adjLat/10));
GLon = U.charAt((int) (adjLon/20));
nLat = ""+(int)(adjLat % 10);
nLon = ""+(int)((adjLon/2) % 10);
rLat = (adjLat - (int)(adjLat)) * 60;
rLon = (adjLon - 2*(int)(adjLon/2)) *60;
gLat = L.charAt((int)(rLat/2.5));
gLon = L.charAt((int)(rLon/5));
String locator = ""+GLon+GLat+nLon+nLat+gLon+gLat;
return locator;
}
}
And here is the JUnit test case:
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class LocationTest {
@Test
public void testLatLonToGridSquare() {
Location loc = new Location(48.14666f,11.60833f);
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "JN58td");
loc = new Location("-34.91","-56.21166");
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "GF15vc");
loc = new Location(38.92f,-77.065f);
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "FM18lw");
loc = new Location(-41.28333f,174.745f);
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "RE78ir");
loc = new Location(41.714775f,-72.727260f);
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "FN31pr");
loc = new Location(37.413708f,-122.1073236f);
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "CM87wj");
loc = new Location(35.0542f,-85.1142f );
System.out.println(loc.maidenhead);
assertEquals(loc.maidenhead, "EM75kb");
}
}
I've included Locator based calculations in PyHamTools - an open source python Library, which is easy to use:
It's as easy as this:
from pyhamtools.locator import calculate_distance, latlong_to_locator
locator1 = latlong_to_locator(48.52, 9.375)
locator2 = latlong_to_locator(-32.77, 152.125)
distance = calculate_heading(locator1, locator2)
print("%.1fkm" % distance)
16466.4km
For more examples and the documentation check out: http://pyhamtools.readthedocs.org/en/latest/index.html
You can start by using a map, but that gets out of hand once you get beyond the second set of digits. There are calculators online, like this one from QRZ or this map-based locator. It's possible to calculate it by hand as well - the Wikipedia article has a good description on how they work.
Basically, the first two characters build a grid, just like on any map. The first character is longitude, the second is latitude, and the first pair is letters A-R, meaning the world is broken up into 18 slices. Since there are 360 degrees of longitude and 180 of latitude, that means that each grid square is about 18 degrees wide by 9 degrees tall. It starts at 180 degrees west and 90 degrees south, so if I'm in the sixth grid east of 180 degrees W (meaning my longitude is between 72 West and 90 W), my first character is F. Then, the second pair of digits divides that up into 10 equal slices, and so on.
I didn't see any answers that had the extended square as referenced in the Wikipedia article. My Clojure implementation (GitHub Gist Here) encodes the fourth and fifth pairs.
(defn to-maidenhead
[lat long]
(let [long (-> long (+ 180) (/ 2))
lat (-> lat (+ 90))
funs [#(* 10 (mod % 1)) #(* 24 (mod % 1)) #(* 10 (mod % 1)) #(* 24 (mod % 1))]
cars [\A \0 \a \0 \a]]
(map (fn [geo]
(map (fn [n car] (char (+ (int car) n)))
(reductions (fn [n fun] (fun n))
(/ geo 10)
funs)
cars))
[long lat])))
(def to-maidenhead-str (comp #(apply str %) #(apply interleave %) to-maidenhead))
A simple bit of testing:
(comment
(apply str
(apply interleave
(to-maidenhead 36.165926 -86.723285)))
;; => "EM66pd39et"
(and
(= "EM66pd39et" (to-maidenhead-str 36.165926 -86.723285))
(= "OF86cx76ql" (to-maidenhead-str -33.014673 116.230695))
(= "FD54oq44oh" (to-maidenhead-str -55.315349 -68.794971))
(= "PM85ge79vh" (to-maidenhead-str 35.205535 136.56579)))
;; => true
;; Just a simple test at various coordinates around the globe.
;; Maidenhead Locator taken from http://no.nonsense.ee/qth/map.html
)
Have a Look at numpy.meshgrid
http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.meshgrid.html
-
1$\begingroup$ Links by themselves aren't very good answers. Perhaps you could add more detail, such as explaining what numpy.meshgrid does and how to use it in a relevant way? $\endgroup$– Kevin Reid AG6YO ♦Dec 26, 2015 at 15:30
The solutions I've seen all seem to be rehashing the same code with little thought to what is actually happening. I decided to write a solution that solves the general case, making it a simple matter to extend the precision of the result to any length. Since I am an iOS developer, the solution is in Swift:
private let upper = "ABCDEFGHIJKLMNOPQRSTUVWX"
private let lower = "abcdefghijklmnopqrstuvwx"
public func maidenhead(latitude: Double, longitude: Double) -> String {
var lonDegrees: Double = 360
var latDegrees: Double = 180
var lon = longitude + 180.0
var lat = latitude + 90.0
var lonRemainder = lon
var latRemainder = lat
func gridPair(divisions: Double) -> (Double, Double) {
lonDegrees = lonDegrees/divisions
latDegrees = latDegrees/divisions
lon = lonRemainder/lonDegrees
lonRemainder = lonRemainder%lonDegrees
lat = latRemainder/latDegrees
latRemainder = latRemainder%latDegrees
return (lon, lat)
}
let (gridLonField, gridLatField) = gridPair(18)
let (gridLonSquare, gridLatSquare) = gridPair(10)
let (gridLonSubSquare, gridLatSubSquare) = gridPair(24)
let (gridLonExtSquare, gridLatExtSquare) = gridPair(10)
let (gridLonSubExtSquare, gridLatSubExtSquare) = gridPair(24)
return "\(upper[Int(gridLonField)])\(upper[Int(gridLatField)])\(Int(gridLonSquare))\(Int(gridLatSquare))\(lower[Int(gridLonSubSquare)])\(lower[Int(gridLatSubSquare)])\(Int(gridLonExtSquare))\(Int(gridLatExtSquare))\(lower[Int(gridLonSubExtSquare)])\(lower[Int(gridLatSubExtSquare)])"
}
Here's the docs on it. http://www.qrz.ru/vhf/qth_h.pdf
And of course you were given this one. https://en.wikipedia.org/wiki/Maidenhead_Locator_System
Take a look at
C# version
public static String LatLonToGridSquare(double lat, double lon)
{
double adjLat, adjLon;
char GLat, GLon;
String nLat, nLon;
char gLat, gLon;
double rLat, rLon;
String U = "ABCDEFGHIJKLMNOPQRSTUVWX";
String L = U.ToLower();
if (double.IsNaN(lat)) throw new Exception("lat is NaN");
if (double.IsNaN(lon)) throw new Exception("lon is NaN");
if (Math.Abs(lat) == 90.0) throw new Exception("grid squares invalid at N/S poles");
if (Math.Abs(lat) > 90) throw new Exception("invalid latitude: " + lat);
if (Math.Abs(lon) > 180) throw new Exception("invalid longitude: " + lon);
adjLat = lat + 90;
adjLon = lon + 180;
GLat = U[(int)(adjLat / 10)];
GLon = U[(int)(adjLon / 20)];
nLat = "" + (int)(adjLat % 10);
nLon = "" + (int)((adjLon / 2) % 10);
rLat = (adjLat - (int)(adjLat)) * 60;
rLon = (adjLon - 2 * (int)(adjLon / 2)) * 60;
gLat = L[(int)(rLat / 2.5)];
gLon = L[(int)(rLon / 5)];
String locator = "" + GLon + GLat + nLon + nLat + gLon + gLat;
return locator;
}
In Javascript, I didn't find a grid square library with Google, though obviously there is existing code on several related ham mapping websites.
So I coded up HamGridSquare.js based in part on K6WRU's answer above.
It passes his tests.
The function definitions are MeteorJS framework compatible and supports various inputs.
A call to latLonFromGridSquare()
can have two numeric or string parameters (lat,lon)
or a single parameter that is an array or object with numeric, string, or function properties ([lat,lon])
({lat: nnn, lon: nnn})
({lat: latGetterFunc, lon: lonGetterFunc})
. The latter should be compatible with the LatLon objects created by Chris Veness' LatLon JS library. Chris' LatLon library provides a way to calculate distances and bearings.
// HamGridSquare.js
// Copyright 2014 Paul Brewer KI6CQ
// License: MIT License http://opensource.org/licenses/MIT or CC-BY-SA
//
// Javascript routines to convert from lat-lon to Maidenhead Grid Squares
// typically used in Ham Radio Satellite operations and VHF Contests
//
// Inspired in part by K6WRU Walter Underwood's python answer
// http://ham.stackexchange.com/a/244
// to this stack overflow question:
// How Can One Convert From Lat/Long to Grid Square
// http://ham.stackexchange.com/questions/221/how-can-one-convert-from-lat-long-to-grid-square
//
latLonToGridSquare = function(param1,param2){
var lat=-100.0;
var lon=0.0;
var adjLat,adjLon,GLat,GLon,nLat,nLon,gLat,gLon,rLat,rLon;
var U = 'ABCDEFGHIJKLMNOPQRSTUVWX'
var L = U.toLowerCase();
// support Chris Veness 2002-2012 LatLon library and
// other objects with lat/lon properties
// properties could be getter functions, numbers, or strings
function toNum(x){
if (typeof(x) === 'number') return x;
if (typeof(x) === 'string') return parseFloat(x);
if (typeof(x) === 'function') return parseFloat(x());
throw "HamGridSquare -- toNum -- can not convert input: "+x;
}
if (typeof(param1)==='object'){
if (param1.length === 2){
lat = toNum(param1[0]);
lon = toNum(param1[1]);
} else if (('lat' in param1) && ('lon' in param1)){
lat = toNum(param1.lat);
lon = toNum(param1.lon);
} else if (('latitude' in param1) && ('longitude' in param1)){
lat = toNum(param1.latitude);
lon = toNum(param1.longitude);
} else {
throw "HamGridSquare -- can not convert object -- "+param1;
}
} else {
lat = toNum(param1);
lon = toNum(param2);
}
if (isNaN(lat)) throw "lat is NaN";
if (isNaN(lon)) throw "lon is NaN";
if (Math.abs(lat) === 90.0) throw "grid squares invalid at N/S poles";
if (Math.abs(lat) > 90) throw "invalid latitude: "+lat;
if (Math.abs(lon) > 180) throw "invalid longitude: "+lon;
adjLat = lat + 90;
adjLon = lon + 180;
GLat = U[Math.trunc(adjLat/10)];
GLon = U[Math.trunc(adjLon/20)];
nLat = ''+Math.trunc(adjLat % 10);
nLon = ''+Math.trunc((adjLon/2) % 10);
rLat = (adjLat - Math.trunc(adjLat)) * 60;
rLon = (adjLon - 2*Math.trunc(adjLon/2)) *60;
gLat = L[Math.trunc(rLat/2.5)];
gLon = L[Math.trunc(rLon/5)];
return GLon+GLat+nLon+nLat+gLon+gLat;
}
testGridSquare = function(){
// First four test examples are from "Conversion Between Geodetic and Grid Locator Systems",
// by Edmund T. Tyson N5JTY QST January 1989
// original test data / citations by Walter Underwood K6WRU
// last test and coding into Javascript from Python by Paul Brewer KI6CQ
var testData = [
['Munich', [48.14666,11.60833], 'JN58td'],
['Montevideo', [[-34.91,-56.21166]], 'GF15vc'],
['Washington, DC', [{lat:38.92,lon:-77.065}], 'FM18lw'],
['Wellington', [{latitude:-41.28333,longitude:174.745}], 'RE78ir'],
['Newington, CT (W1AW)', [41.714775,-72.727260], 'FN31pr'],
['Palo Alto (K6WRU)', [[37.413708,-122.1073236]], 'CM87wj'],
['Chattanooga (KI6CQ/4)', [{lat:function(){ return "35.0542"; },
lon: function(){ return "-85.1142"}}], "EM75kb"]
];
var i=0,l=testData.length,result='',thisPassed=0,totalPassed=0;
for(i=0;i<l;++i){
result = latLonToGridSquare.apply({}, testData[i][1]);
thisPassed = result===testData[i][2];
console.log("test "+i+": "+testData[i][0]+" "+JSON.stringify(testData[i][1])+
" result = "+result+" expected= "+testData[i][2]+
" passed = "+thisPassed);
totalPassed += thisPassed;
}
console.log(totalPassed+" of "+l+" test passed");
return totalPassed===l;
}
-
$\begingroup$ Probably a long shot, but would you know how to extend this code to also take a distance parameter and return an array of grid squares in a circle with the distance as the radius? $\endgroup$– Steve EJul 16, 2016 at 4:51
-
$\begingroup$ @SteveE there are libraries that you can give a lat long a heading and a distance and get back the destination coordinates. Call that in a loop as heading is varied and convert all the destination coordinates to grid squares. Then clean it up by sorting the list and removing duplicates. Good luck. $\endgroup$– PaulJul 16, 2016 at 5:03
-
$\begingroup$ @SteveE your radius question sounds like it would be a good OP. Ask it as a new and separate question, even if you first asked it here, as a comment, back in 2016. I wouldn't save only an array of grids, but also with each grid, the azimuth and distance to the grid, center to center, measured from the center of your circle. One or two extra functions? Seems like that may ultimately be useful information in your quest, or not? $\endgroup$ Nov 27, 2021 at 13:37
-
$\begingroup$ @SteveE As a new separate OP, you can further explain your goals which might invite more creative approaches. $\endgroup$ Nov 27, 2021 at 13:44
Just an improvement for one of the answers above from Ossi Väänänen starting with this:
void calcLocator(char *dst, double lat, double lon) {
[...]
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)o3 + 'A';
dst[5] = (char)a3 + 'A';
dst[6] = (char)0;
}
Consider converting the dst values to ascii characters. Replace the dst array entries above with the following:
dst[0] = char(o1+65) ;
dst[1] = char(a1+65) ;
dst[2] = char(o2+48) ;
dst[3] = char(a2+48) ;
dst[4] = char(o3+97);
dst[5] = char(a3+97) ;
Plus 65 gives you the ascii letter values for upper case, +48 for numbers, and +97 for lower case.
Example results as follows:
char grid[4];
calcLocator(grid,gps.location.lat(),gps.location.lng());
Serial.print(grid);
// results will be in the following format:
AB01ab
73's all