Close

Maidenhead

A project log for Careless WSPR

A desultorily executed Weak Signal Propagation Reporter beacon.

ziggurat29ziggurat29 08/10/2019 at 16:220 Comments

Summary

A Lat/Lon-to-Maidenhead support routine is produced.

Deets

WSPR (and other amateur (ham) radio things, and even some other folks, too) like to express location in terms of a 'Maidenhead Grid Square Locator'.  To wit it's named after Maidenhead, UK.  If you were to take the globe and do what is called an 'equirectangular projection':

source: By Strebe - Own work, CC BY-SA 3.0

wikipedia: Equirectangular Projection

then the maidenhead is simply a scheme for encoding the latitude and longitude into an alternative form.  This is useful to hams because it is more compact to transmit than to spell out all the digits of the numeric representation.  Also, for many purposes, the extra precision is not needed, so a few characters suffice.

The encoding scheme is straightforward:

  1. start with lat long and progressively shift out most significant chunks of resolution.  The first chunk is special -- the remaining ones are regular and based on 10.
  2. for each chunk, two symbols will be emitted.  The first is for the encoding of the longitude portion, and the second is for the latitude portion.  Alternate chunks use a different encoding:  alphabetic or numeric.  The first chunk uses alphabetic, the second uses numeric, the third uses alphabetic again, and so forth.  By convention, the first alphabetic chunk uses uppercase, and the remainder uses lower case, but strictly the system is case-insensitive.
  3. repeat to any desired resolution.

The reverse decoding is similarly straightforward but I have not implemented that here.

But the Metric

While the US may be the brunt of many jokes about not having adopted to the metric system like the rest of the world, there are aspects of the metric system that veritably no one has adopted.  In this case, nearly everyone still does angular measurement in the system Sumerians devised based on 60 https://en.wikipedia.org/wiki/Sexagesimal, rather than the metric system.  (To wit, some civil engineering aspects such as surveying do use 'grads' -- the metric equivalent to 'degrees'.)  I do find it amusing that we use a system from about 7,000 years ago that was ostensibly created to make things easier on working with your fingers (counting to a high number on one hand) to in the modern technological times still also being used to make things easier on working with your fingers (communicating your location via Morse code).  At any rate, this is why the first part of the maidenhead is treated specially.  After that it's handled more uniformly.

Code

The first part is to precondition the data.  To the longitude is added 180 to make it go from 0 to 360, and similarly to the latitude is added 90 to make it go from 0 to 180.  The second part is to then do a 'base-18' encoding of those data by dividing the longitude into 18 zones of 20 degrees, and the latitude into 18 zones of 10 degrees.  These are encoded as the symbols 'A' through 'R', upper-case by convention, and are emitted with the longitude first and the lattitude second.  This first pair is called a 'fields' and gets of off the sexagesimal.  The remaining are encoded more consistently.

The remaining bits of resolution are done in much of the same way, but alternate between using the digits '0' - '9', or the letters 'a' - 'x'.  So, when working on a digital portion, the encoding is base-10, and when working on an alphabetic portion the encoding is base-24.  By convention, the lower-case letters are used.  These are called 'squares' and 'subsquares'.  You can repeat this process to arbitrary precision.  In this project we only use 4 symbols because that is what the WSPR protocol requires, but the code does not have that limitation.  Speaking of code, this is it:

//north latitude is positive, south is negative
//east longitude is positive, west is negative
int toMaidenhead ( float lat, float lon, 
        char* achMaidenhead, unsigned int nDesiredLen )
{
    if ( nDesiredLen < 2 || nDesiredLen & 0x01 )    //silly cases
    {
        return 0;
    }
    int maxprec = nDesiredLen / 2;

    //bounds check lon[-180, +180]
    //bounds check lat[-90, +90]
    if (lon < -180.0F || lon > 180.0F)
    {
        return 0;
    }
    if (lat < -90.0F || lat > 90.0F)
    {
        return 0;
    }

    int lonquo, latquo;
    float lonrem, latrem;

    //18 zones of long of 20 deg; 18 zones of lat of 10 deg
    lonquo = (int)((lon + 180.0F)/20.0F);
    lonrem = (float) fmod ( lon + 180.0F, 20.0F );
    latquo = (int)((lat + 90.0F)/10.0F);
    latrem = (float) fmod ( lat + 90.0F, 10.0F );

    char* pchOut = achMaidenhead;

    (*pchOut++) = ('A' + lonquo);
    (*pchOut++) = ('A' + latquo);

    lonrem /= 2.0F;

    int prec = 1;
    while ( prec < maxprec )
    {
        ++prec;
        lonquo = (int)(lonrem/1.0F);
        lonrem = (float) fmod ( lonrem,1.0F );
        latquo = (int)(latrem/1.0F);
        latrem = (float) fmod ( latrem,1.0F );
        if (prec & 0x01)
        {
            (*pchOut++) = ('a' + lonquo);
            (*pchOut++) = ('a' + latquo);
            lonrem *= 10.0F;
            latrem *= 10.0F;
        }
        else
        {
            (*pchOut++) = ('0' + lonquo);
            (*pchOut++) = ('0' + latquo);
            lonrem *= 24.0F;
            latrem *= 24.0F;
        }
    }

    (*pchOut) = '\0';

    return 1;
}

I put in a little temporary test code in main.c to exercise the function completely and it seems to be working as intended.  I removed that test code since it was just temporary.

Next

Persistent settings

Discussions