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?

  • 1
    Do you seek an algorithm, or an application? – VU2NHW Oct 23 '13 at 12:32
  • 1
    An algorithm really, but application could be useful to others out there. – PearsonArtPhoto Oct 23 '13 at 12:43

13 Answers 13

up vote 21 down vote accepted

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

  1. 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.
  2. 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.
  3. 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

  1. 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.
  2. 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.
  3. 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()

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");
  }
}

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;
}

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'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

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
    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? – Kevin Reid AG6YO Dec 26 '15 at 15:30

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

http://aprs.fi/

http://aprs.fi/page/api

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;
}
  • 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? – Steve E Jul 16 '16 at 4:51
  • @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. – Paul Jul 16 '16 at 5:03

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)])"
}

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;
}

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

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.