C library to convert between mapcodes and latitude/longitude

 

Version 1.40

Copyright ©2003-2014 by The Mapcode Foundation

 

 

1. Converting a coordinate into a mapcode

 

int encodeLatLonToMapcodes(

char**            results,

double            lat,

double            lon,

int       territoryCode,

int       extraDigits)

 

find all possible mapcodes in a given territory (or in ALL territories)

input:

output:


 

int encodeLatLonToSingleMapcode(

char*  result,

double            lat,

double            lon,

int       territoryCode,

int       extraDigits)

 

 

find ONE possible mapcode in a given territory (or in ALL territories)

input:

output:

 

Example code:

 

  double lat = 52.376514;

  double lon =  4.908542;

  const char* countryabbr = "NLD";

 

  char* r[MAX_NR_OF_MAPCODE RESULTS]; // Strings allocated by library!

  int tc = convertTerritoryNameToCode(countryabbr, 0);

  int nrresults = encodeLatLonToMapcodes(r, lat, lon, tc, 0);

  if (nrresults > 0)

    printf("%0.6f,%0.6f has mapcode %s %s\n", lat, lon, r[1], r[0]);

  else

    printf("No results\n");

 

Output:

 

52.376514,4.908542 has mapcode NLD 49.4V

 

Thus, convertTerritoryNameToCode is used to determine the territory code tc for ÒNLDÓ. Then encodeLatLonToMapcodes is used to determine the mapcode for the coordinate 52.376514,4.908542. The first one of those (if any) is printed.

 

Note that there may be more than one result, even for a single territory.


When you only want ONE result, you could also write:

 

  double lat = 52.376514;

  double lon =  4.908542;

  const char* countryabbr = "NLD";

 

  char result[MAX_MAPCODE_RESULT_LEN]; // String allocated by caller!

  int tc = convertTerritoryNameToCode(countryabbr, 0);

  int nrresults = encodeLatLonToSingleMapcode(result, lat, lon, tc, 0);

  if (nrresults > 0)

    printf("%0.6f,%0.6f has mapcode %s\n", lat, lon, result);

  else

    printf("No results\n");

 

You can also generate all possible mapcodes, irrespective of territory, by passing territoryCode=0:

 

  int i;

  double lat = 52.376514;

  double lon =  4.908542;

 

  char *r[MAX_NR_OF_MAPCODE_RESULTS];

  int nrresults = encodeLatLonToMapcodes(r, lat, lon, 0, 0);

 

  printf("%d mapcodes for %0.6f,%0.6f:\n", nrresults, lat, lon);

  for (i = 0; i < nrresults; i++) {

 

    // show context (unless it is international)

    if (strcmp(r[(i * 2)  +1], "AAA") !=0 )

      printf("%s ",r[(i * 2) + 1]);

 

    // show mapcode

    printf("%s\n", r[i * 2] );

  }

 

Output:

 

5 possible mapcodes for 52.376514,4.908542:

NLD 49.4V

NLD G9.VWG

NLD DL6.H9L

NLD P25Z.N3Z

VHXGB.1J9J

 

Note that encodeLatLonToMapcodes(r, lat, lon, 0, e) is guaranteed to return at least one result if lat, lon is in range (i.e. on Earth). Again, the elements of r are made to point to territory abbrevations and mapcodes in those territories. Within territories the shortest mapcode code is listed first. The territories are listed in no particular order, except that the international code is always the last code in the list.

 


2. Converting a mapcode into a coordinate

 

int decodeMapcodeToLatLon(

            double*          lat,

            double*          lon,

            const char*    mapcode,

            int                   territoryCode)

 

input:

output:

 

Example code:

 

  const char* userinput = "NLD 49.4V";

  double lat, lon;

  int err = decodeMapcodeToLatLon(&lat, &lon, userinput, 0);

  if (err)

    printf("\"%s\" is not a valid mapcode\n", userinput);

  else

    printf("\"%s\" represents %0.6f,%0.6f\n", userinput, lat, lon);

 

Output:

 

  "NLD 49.4V" represents 52.376514,4.908542

 

Note that the above piece of code will only ÒaccidentallyÓ handle ambiguous partial mapcodes correctly. For example,

userinput=ÓIN VY.HVÓ

will either be interpreted as an abbreviation of ÒUS-IN VY.HVÓ or of ÒRU-IN VY.HVÓ, and thus either produce a coordinate in Indiana, USA, or in Ingushetia, Russia.

Passing a Òdefault contextÓ improves the chances of ambiguous user input to be interpreted Òas the user intendedÓ. Thus, if one builds a system that is mostly going to be used in the USA, the following hard-codes that preference (i.e. Òwhen in doubt, assume the ÒUSAÓ):

 

  int defaultcontext = convertTerritoryNameTCode("USA",0);

  const char* userinput = "IN VY.HV";

  double lat, lon;

  int err = convertMapcodeToLatLon(&lat, &lon, userinput,

              defaultcontext);

  if (err)

    printf("\"%s\" is not a valid mapcode\n", userinput);

  else

    printf("\"%s\" represents %0.6f,%0.6f\n", userinput, lat, lon);

 

Output:

 

  "IN VY.HV" represents 39.727950,-86.118444

 

A more sophisticated system could of course make much better assumptions, for example based on the GPS coordinate of the user, or the current cursor position on a world map that is being displayed to the user.

 

In an interactive system, the best way to handle ambiguity is probably to always use the most recent successful, explicitly stated context as default.

For example, suppose you remembered the previous correctly interpreted user input:

 

  const char* previous_successful_input = "RU-IN DK.CN0";

 

then the following code snippet will correctly interpret the completely abbreviated mapcode Ò49.4VÓ as being in the same state (i.e. RU-IN):

 

  int previouscontext = convertTerritoryNameToCode(

                          previous_successful_input,0);

  const char* userinput = "D6.58";

  int err = decodeMapcodeToLatLon(&lat, &lon, userinput,

              previouscontext);

 

Output:

 

  "D6.58" represents 43.259275,44.771980

 

which is in  Ingushetia Republic. And in fact, had we written

 

  userinput=ÒAL D6.58Ó

 

this would generate the output

 

  "AL D6.58" represents 51.958335,85.975322

 

which is in the Russian republic of Altai: because of the context RU-IN, the territory has been interpreted as RU-AL, instead of the equally likely US-AL (Alabama, USA) or BR-AL (Alagoas, Brazil).


Here is an example that decodes consecutive user inputs, some of them wildly ambiguous. With the exception of the very first input, all are probably interpreted as the user intended:

 

  int previouscontext = -1; // no initial context

 

  const char* userinput[] = {

    "49.4V",

    "US-IN 49.4V",

    "49.4V",

    "AL 49.4V",

    "RU-IN 49.4V",

    "AL 49.4V",

    "NLD XXX.YYY",

    "49.4V",

    "CCCCC.CCCC",

    "49.4V",

    NULL

  };

 

  int i;

  for (i = 0; userinput[i] != 0; i++) {

    double lat, lon;

    int err = decodeMapcodeToLatLon(&lat, &lon, userinput[i],

                previouscontext);

    if (err)

      printf("\"%s\" is not a valid mapcode\n", userinput[i]);

    else {

      int newcontext = convertTerritoryNameToCode(userinput[i], 0);

      if (newcontext>1 && (newcontext < MAX_MAPCODE_TERRITORY_CODES))

        previouscontext = newcontext;       

      printf("%12s represents %0.6f,%0.6f\n", userinput[i], lat, lon);

    }

  }

 

Output:

 

"49.4V" is not a valid mapcode

 US-IN 49.4V represents 39.783750,-86.198832

       49.4V represents 39.783750,-86.198832

    AL 49.4V represents 33.532750,-86.836184

 RU-IN 49.4V represents 43.249285,44.741353

    AL 49.4V represents 51.927091,85.910319

 NLD XXX.YYY represents 51.204536,5.541607

       49.4V represents 52.376514,4.908542

  CCCCC.CCCC represents -16.326209,-48.016851

       49.4V represents 52.376514,4.908542

 


Explanation:

 

"49.4V" is not a valid mapcode

Since there is no previous context, this ambiguous mapcode can simply not be interpreted. For this reason, it may be a good idea to choose a better default context than previouscontext=-1 (e.g. based on the userÕs GPS position).

 

US-IN 49.4V represents 39.783750,-86.198832

A complete and unambiguous mapcode, it results in a coordinate in Indiana, USA.

 

49.4V represents 39.783750,-86.198832

Since the previous input was in Indiana, this incomplete mapcode is encoded in the same context.

 

AL 49.4V represents 33.532750,-86.836184

Since the previous input was in the Indiana, USA, the context ÒALÓ is interpreted as another state in the USA, and thus results in a coordinate in Alabama (rather than, say, the state of Alagoas in Brazil).

 

RU-IN 49.4V represents 43.249285,44.741353

A complete and unambiguous mapcode, it results in a coordinate in Ingushetia, Russia.

 

AL 49.4V represents 51.927091,85.910319

Unlike two inputs back, AL 49.4V is now interpreted in Russia (the Altai Republic) rather than in the USA (Alabama), since the most recent context was Russian.

 

NLD XXX.YYY represents 51.204536,5.541607

A complete and unambiguous mapcode, it results in a coordinate in The Netherlands.

 

49.4V represents 52.376514,4.908542

Since the previous input was in The Netherlands, this time 49.4V is interpreted in The Netherlands as well.

 

CCCCC.CCCC represents -16.326209,-48.016851

A complete and unambiguous international mapcode. Although it decodes to a coordinate somewhere in Brazil, the mapcode does not explicitly specify Brazil as a context. Therefore, the context for future inputs will remain ÒThe NetherlandsÓ.

 

49.4V represents 52.376514,4.908542

Since the most recent explicit context was in The Netherlands, this ambiguous mapcode is now also interpreted in The Netherlands.


2.1 Recognizing an input as a mapcode

 

Sometimes you may wish to allow a user to input something in a ÒgeneralÓ search box – an address, a coordinate, a mapcode, or something else.

The following routine is very  efficient and lightweight, and recognizes a user input that is intended as a mapcode. For example:

            ÒNLD 503.XE2Ó

is intended as a mapcode (although it happens to be invalid), while

            ÒSt. Jacobs Street 45, LondonÓ

Is not. Since anything that looks like a mapcode is very unlikely to represent anything else, we would recommend to handle (i.e. decode) anything that looks like a mapcode as a mapcode. If it fails to decode, you could still try to interpret as something else, but as has been said: it is very unlikely it does represent something else.

 

 

int compareWithMapcodeFormat(

            const char*    string ,

            int                   includesTerritory)

 

input:

output:

 


2.2 Extra precision

 

Mapcodes are intended for easy, daily use. They were therefore made short, and no more precise than is necessary to be useful at the ÒhumanÓ scale: accurate to within 5 meters – or put another way: inaccurate by up to 5 meters. On average, inaccurate by 2.5 meters. An inaccuracy, by the way, both in latitude and longitude.

 

For special applications – e.g. for internal storage of original latitude/longitude pairs – mapcode was extended to allow one or two extra letters. One letter extra decreases the worst-case inaccuracy to about 1 meter, two letters decreases it to about 20 centimeters.

 

As an example, consider coordinate 52.376496, 4.908583. When encoded, it yields mapcode NLD 49.4V. This mapcode decodes back into 52.376514, 4.908542, a coordinate which is 2.83 meters off to the west, and 1.94 meters too far north (in combination, the mapcode is 3.44 meters away from the original coordinate).

When we encode the same coordinate with an extra digit, we get NLD 49.4V-R, a mapcode that decodes into 52.376491, 4.908574, only 96 centimeters off. With two extra digits, we get a mapcode that is about 13 centimeters off.

 

Mapcode:           decodes into:                           error vs original coordinate:

49.4V                 52.376514, 4.908542              3.44 meter

49.4V-R             52.376491, 4.908574              0.96 meter

49.4V-R5           52.376497, 4.908584              0.13 meter

 

Note that this is just an example. Had we encoded 52.376514, 4.908542, the mapcode 49.4V would be 100% precise. It is not the actual error, but the maximum error that is reduced by adding extra letters to a mapcode.

 

PLEASE NOTE: the above may make it seem that it is a good idea to always add extra letters. This would defeat the core purpose of the mapcode system, which is to be accurate enough for daily, human-scale use. The high-precision extension was made for very special applications only. What it may also be useful for is for internal storage, e.g. when your original coordinates are that precise, you could consider storing the precise mapcodes in your database in high-precision format. We still recommend that you display normal mapcodes (i.e. without the extension) to the end-users of your system!

 


3. Routines related to territories

 

The mapcode system is based on an official code table, which in turn is based on the ISO 3166 standards.

For efficiency, these codes need to be converted into internal Òterritory codesÓ. For these, the following three support routines are relevant:

 

int convertTerritoryNameToCode(

            const char*    isoName,

            int                   parentTerritoryCode)

 

 

const char* convertTerritoryCodeToIsoName(

            int       territoryCode,

            int       format)

 

 

int getCountryOrParentCountry(int territoryCode)

 


4. Routines related to Unicode and/or foreign alphabets

 

const char* decodeToRoman(const UWORD* string);

 

Mapcodes may be specified in other alphabets. This routine converts a 16-bit user input (i.e. a string of 16-bit Unicode characters) into a roman equivalent – which can then be offered to mc2coord;

 

Note that the return pointer will be overwritten by the next call to decode_to_roman.

 

const UWORD* encodeToAlphabet(

            const char*    mapcode,

            int                   alphabet);

 

This routine converts a normal (roman-alphabet) mapcode into a Unicode string in the specified alphabet. For example, it can converty

PQ.RS to नप.भम (hindi alphabet)

PQ.RS to РФ.ЯЦ (Russian alphabet)

 

Note that the return pointer will be overwritten by the next call to encode_to_alphabet.

 

At the date of writing, the following 4 alphabets have been approved for official use, the others have not yet officially been approved.

0 = roman,

2 = cyrillic,

4 = hindi,

12 = gurmukhi

 

Version history

1.25 initial release to the public domain

1.26 added alias OD ("Odisha") for indian state OR ("Orissa")

1.27 improved (faster) implementation of the function isInArea

1.28 bug fix for the needless generation of 7-letter alternatives

            to short mapcodes in large states in India

1.29 also generate country-wide alternative mapcodes for states

1.30 updated the documentation and extended it with examples and suggestions

1.31 added lookslikemapcode();

1.32 added coord2mc1();fixed 1.29 so no country-wide alternative is produced
            in edge cases; prevent FIJI failing to decode at exactly 180 degrees;

1.33 fix to not remove valid results just across the edge of a territory;

improved interface readability and renamed methods to more readable forms.

1.40 added extraDigits parameter so that high-precision mapcodes can be generated