Doorgaan naar hoofdcontent

Maidenhead locator to Degrees and back again

http://www.hamradio.in/circuits/grid_locator_system.php

This code will convert a Maidenhead locator to Latitude/Longitude in degrees.
It seems that everyone tries to implement this, but I can not find any clean source code...

So here is my implementation, 2 ways, from lat,long -> maidenhead and reverse...
This is plain "C" code tested on linux, but I will include it on my STM32F4 dev board.

typedef struct {
    double latitude;
    double longitude;
    int errorcode;
} lat_lon_t;

uint8_t to_upper(char c) {
    if (c >= 'a' && c <= 'z') {
        return c - 0x20;
    }
    return c;
}

// the equator and the 0 meridian are the reference
// we call them +90 (instead of 0) and +180 (instead of 0) to prevent negative numbers
// the world is divided into 18 * 18 fields (324 fields total)
// these fields are sub-divided into squares and sub-squares and sub-sub-squares etc forever
// but only 5 iterations make sense (size of a house)
// and work on http://www.k7fry.com/grid/?qth=JO21TL51XT  JO21QM83BI
// to divide the world into a 18x18 grid the size of the grid is 20 by 10 degrees
// 360 deg / 18 => 20 degrees horizontal (longitude)
// 180 deg / 18 => 10 degrees vertical (latitude)
// http://www.wikiwand.com/en/Maidenhead_Locator_System

lat_lon_t maidenhead_to_latlon(const char *mhl) {

    lat_lon_t value = {0};

    static const double table[] = { /* scaling for maidenhead grid to degrees latitude */
            20.0,
            2.0,
            (1.0 / 12.0),
            (1.0 / 120.0),
            (1.0 / 2880.0),
            (1.0 / 28800.0),
            (1.0 / 6912000.0),
            (1.0 / 69120000.0),
            (1.0 / 165888000.0)
    };

    size_t len = strlen(mhl);

    if ((len % 2) != 0) {
        // odd number of chars, invalid
        value.errorcode = 1;
        return value;
    }

    if (len > (sizeof(table) / sizeof(table[0]) * 2)) {
        // too long maidenhead string
        value.errorcode = 2;
        return value;
    }

    // loop iterations
    int j = 0;

    double lat_tmp = 0.0f; // result latitude in degrees
    double lon_tmp = 0.0f; // result longitude in degrees

    char uc; // grid latitude cell number
    char ud; // grid longitude cell number

    for (size_t i = 0; i < len; i += 2) {
        if (j % 2 == 0) {
            // A
            uc = to_upper(*(mhl + i)) - 'A';
            ud = to_upper(*(mhl + i + 1)) - 'A';
        } else {
            // 0
            uc = *(mhl + i) - '0';
            ud = *(mhl + i + 1) - '0';
        }
        lon_tmp += uc * table[j];
        lat_tmp += ud * table[j];
        j++;
    }

    // table was in latitude, fix scale by 2
    lat_tmp /= 2.0f;

    // offset of MH grid to GPS
    lon_tmp -= 180.0f;
    lat_tmp -= 90.0f;

    // prepare return value
    value.latitude = lat_tmp;
    value.longitude = lon_tmp;

    return value;
}


int lat_lon_to_maidenhead(lat_lon_t location, char *resultbuf, uint8_t iterations) {
    // convert location to Maidenhead locator string
    // store into buffer (at least 16 bytes)
    // return 0 on success

    double tmp_lat = location.latitude;
    double tmp_lon = location.longitude;

    char mhstr_tmp[16] = {0}; // result will be here eg "JO32tl" for 51.465779 N 5.633263 E

    tmp_lon = (tmp_lon + 180.0f) / 20.0f; // offset from 0 meridian half a rotation, then divide by 20 to make 18
    tmp_lat = (tmp_lat + 90.0f) / 10.0f;  // offset from equator, then divide by 10 to make 18

    uint8_t i = 0; // loop counter
    char ref; //'A' or 'a'
    char *ptr = mhstr_tmp; // first position of result string

    while (i < iterations) {

        int loni = (int) (tmp_lon);
        int lati = (int) (tmp_lat);

        // odd or even iteration
        if (!(i & 0x01U)) {
            ref = (i > 1) ? 'a' : 'A'; // lower case after first grid

            // number to grid 0->'A' or 0->'a' etc
            *ptr++ = ref + loni;
            *ptr++ = ref + lati;
            *ptr = 0;

            // zoom in on grid
            tmp_lon = (tmp_lon - loni) * 10.0;
            tmp_lat = (tmp_lat - lati) * 10.0;

        } else {
            // number to grid 0->'0' etc
            *ptr++ = '0' + loni;
            *ptr++ = '0' + lati;
            *ptr = 0;

            // zoom in on grid
            tmp_lon = (tmp_lon - loni) * 24.0;
            tmp_lat = (tmp_lat - lati) * 24.0;

        }
        i++;
    }

    printf("Lat %f Long %f => Maidenhead : \"%s\" \n", location.latitude, location.longitude, mhstr_tmp);
    strcpy(resultbuf, mhstr_tmp);
    return 0;
}


int main(int argc, char **argv) {

    const char testval[] = "JO21tl51xs";
    lat_lon_t lat_lon = maidenhead_to_latlon(testval);

    printf("rv=%d\n", lat_lon.errorcode);
    printf("%s => lat=%G lon=%G\n", testval, lat_lon.latitude, lat_lon.longitude);

    // lets convert it back to maidenhead
    char buffer[16] = {0};
    if (0 == lat_lon_to_maidenhead(lat_lon, buffer, 5))
        printf("Coverted back : %s\n", buffer);
    else
        printf("conversion error\n");

    exit(0);
}
// =========================
$ gcc test.c -o test

$ ./test

JO21tl51xs => lat=51.4656 lon=5.63299
Lat 51.465625 Long 5.632986 => Maidenhead : "JO21tl51xs" 
Coverted back : JO21tl51xs

This code I wrote (c) 2019 Edwin van den Oetelaar, MIT license.



Reacties