/** * MAIDENHEAD.C * A program to convert between Maidenhead locators and GPS coordinates. * * This program was written by Aura Vulpes. * It's licensed under 0-BSD. **/ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include int main(int argc, char * argv[]) { char c; int i = 0; char * maidenhead; int precision = 2, coords_mode = 1; /* mode: 1 = coords->maidenhead; 0 = maidenhead->coords */ /* By default, we use square precision (e.g. LI15) */ double latitude = 0, longitude = 0; /* Parses the options passed in to the program L: puts program into coordinate mode, takes a latitude and longitude as float inputs from -90 to +90 and -180 to +180. -90 (90 S) is the south pole, +90 (90 N) is the north pole; -180 (180 W) and +180 (180 E) are both the antimeridian of Greenwich with 0 being the prime meridian. These are both normalized to be positive because that's how Maidenhead works, which is kinda convenient. M: puts program into maidenhead mode, stores where the actual Maidenhead is located inside argv so we can use it later. p: tells us how many pairs of letters/numbers we want to generate when in coordinate mode. */ while((c = getopt(argc, argv, "LMp")) != -1) { switch(c) { case 'L': latitude = atof(argv[optind++]) + 90; longitude = atof(argv[optind++]) + 180; coords_mode = 1; break; case 'M': maidenhead = argv[optind++]; coords_mode = 0; break; case 'p': precision = atoi(argv[optind++]); break; } } /* Not much point doing anything if we have a precision less than 1 */ if(precision < 1) { printf("Error: Precision must be 1 or more!\n"); exit(1); } if(coords_mode) { /* If in coordinates mode, generate Maidenhead locator. NB: When printing coordinates, we print as a positive number plus the hemisphere rather than using positive or negative. So, like, we print "30 S, 30 W" rather than "-30, -30"). */ printf("Coords mode; Latitude: %g %c, Longitude: %g %c, precision: %d\n", fabs(latitude - 90), (latitude - 90) < 0 ? 'S' : 'N', fabs(longitude - 180), (longitude - 180) < 0 ? 'W' : 'E', precision); maidenhead = calloc(precision * 2 + 1, sizeof(char)); /* Generate field. Each field is 20 degrees longitude by 10 degrees latitude. Letters range from A to R, though 90 degrees N is a bit weird. (How convenient to be using ASCII and not EBCDIC!) Once field is located, get lat/long difference within field. (e.g. 34 N is 14 degrees north of 20 N. */ maidenhead[i++] = (longitude / 20) + 65; longitude = fmod(longitude, 20); maidenhead[i++] = (latitude / 10) + 65; latitude = fmod(latitude, 10); if(precision > 1) { /* If desired, generate square. Each square is 2 degrees longitude by 1 degree latitude. Squares are given with digits 0 through 9. Once square is located, get lat/long difference within square. */ maidenhead[i++] = (longitude / 2) + 48; longitude = fmod(longitude, 2); maidenhead[i++] = (latitude / 1) + 48; latitude = fmod(latitude, 1); } if(precision > 2) { /* If desired, generate subsquare. Each subsquare is 5 minutes longitude by 2.5 minutes latitude. (We turn this into decimal to make this easier.) Subsquares are given with letters A through X. */ double dec_conv = 5.0 / 60.0; maidenhead[i++] = (longitude / dec_conv) + 65; longitude = fmod(longitude, dec_conv); dec_conv /= 2; maidenhead[i++] = (latitude / dec_conv) + 65; latitude = fmod(latitude, dec_conv); } if(precision > 3) { /* If desired, generate extended square. Each extended square is 30 seconds longitude by 15 seconds latitude. Extended squares are given with digits 0 through 9. (Have fun with your microwave nonsense!) */ double dec_conv = (30.0 / 60.0) / 60.0; maidenhead[i++] = (longitude / dec_conv) + 48; longitude = fmod(longitude, dec_conv); dec_conv /= 2; maidenhead[i++] = (latitude / dec_conv) + 48; latitude = fmod(latitude, dec_conv); } if(precision > 4) { /* I debated actually including this, but IARU defines it, so... If desired, generate extended subsquare. Each extended subsquare covers, like, 1/24 the resolution of the extended square. (Something, like, 0.00035 degrees longitude and half that for latitude.) Extended subsquares are given with letters A through X. (Have fun with your really short range microwave nonsense!) */ double dec_conv = ((30.0 / 60.0) / 60.0) / 24.0; maidenhead[i++] = (longitude / dec_conv) + 65; longitude = fmod(longitude, dec_conv); dec_conv /= 2; maidenhead[i++] = (latitude / dec_conv) + 65; latitude = fmod(latitude, dec_conv); } /* Terminate the string and print it. */ maidenhead[i] = '\0'; printf("Maidenhead: %s\n", maidenhead); } else { /* If in Maidenhead mode, get coordinates from locator. */ printf("Maidenhead mode; Maidenhead: %s\n", maidenhead); /* We start with the square increments of 20x10 and work down from there. I'll explain next_*_div in a second. */ float longitude_dec = 20; float latitude_dec = 10; float next_lon_div = 0; float next_lat_div = 0; /* Unlike coordinate mode, we allow unlimited precision here. As long as your locator keeps going, we keep generating coordinates. Of course, we use the pattern of alternating between dividing based on 24s and based on 10s. This may or may not be how the locator you're using actually does it. Have fun with that. */ while(maidenhead[i] != '\0') { if(i % 2 == 0) { /* If we're on an even index (i % 2 == 0), we're looking at longitude. If we're on an even pair ((i/2)%2 == 0), we use letters, not numbers. It's here we get to be thankful to be using ASCII and not EBCDIC ;) */ longitude += ((maidenhead[i] - (((i / 2) % 2) == 0 ? 65 : 48)) * longitude_dec); /* If we're on an even pair, the next one will give us 1/10th the resolution. Otherwise, it will be 1/24th the resolution. So, like, the square is 1/10th the longitude of the field. The subsquare is 1/24th the longitude of the square. So on, so forth. */ next_lon_div = ((i / 2) % 2) == 0 ? 10.0 : 24.0; longitude_dec /= next_lon_div; } else { /* Odd indices correspond to longitude. Everything else works as above. */ latitude += ((maidenhead[i] - (((i / 2) % 2) == 0 ? 65 : 48)) * latitude_dec); next_lat_div = ((i / 2) % 2) == 0 ? 10.0 : 24.0; latitude_dec /= next_lat_div; } i++; } /* Per IARU, each locator refers to the center specifically. So, we need to undo our last division of the resolution and add half to both longitude and latitude. So, like, JJ does not refer to the intersection of the equator and prime meridian at 0, 0. It refers to the center of that square, located at 5 N, 10 E. So, that's why we do this. */ longitude_dec *= next_lon_div; latitude_dec *= next_lat_div; longitude += longitude_dec / 2; latitude += latitude_dec / 2; /* We also need to get the coordinates back into a useful format for printing. After all, while having the south pole be 0 and the north pole be 180 is useful for calculations, the way coordinates actually work is that the south pole is -90 and the north pole is +90. */ longitude -= 180; latitude -= 90; /* We don't want to give false precision in the coordinates we output. Granted, we probably still do anyways, but, like, we don't want to print 5 digits past the decimal if you only put in a field locator. So, if you put in a field or square locator (precision = 1 or 2), which would only give to the nearest longitude line (or half a latitude line for squares), we only print 3 digits; if you put in a subsquare locator, we given 6 digits; and if you give anything past that, we give 9 digits. */ precision = strlen(maidenhead) / 2; switch(precision) { case 1: precision = 3; break; case 2: precision = 3; break; case 3: precision = 6; break; default: precision = 9; break; } /* Print the final coordinates as coordinate and hemisphere (e.g. 30 N, 40 W rather than 30, -40) We do still use decimate degrees rather than DMS for simplicity. */ printf("Coordinates: %.*g %c %.*g %c\n", precision, fabs(latitude), latitude < 0 ? 'S' : 'N', precision, fabs(longitude), longitude < 0 ? 'W' : 'E'); } }