C library to convert between
mapcodes and latitude/longitude
Version 1.40
Copyright ©2003-2014 by The Mapcode Foundation
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.
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.
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:
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!
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)
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
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