From 606bc87414e691c958bd86fa9ac86742e41feb00 Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Thu, 20 Aug 2015 13:49:45 +0200 Subject: [PATCH 1/2] Minor improvements Protect binfindmatch against too-long territory strings, or lowercase strings; --- mapcodelib/mapcoder.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/mapcodelib/mapcoder.c b/mapcodelib/mapcoder.c index fa4fa0a..3f1b181 100644 --- a/mapcodelib/mapcoder.c +++ b/mapcodelib/mapcoder.c @@ -14,9 +14,10 @@ * limitations under the License. */ -#include // strlen strcpy strcat memcpy memmove strstr strchr memcmp +#include // strlen strcpy strcat memcpy memmove strstr strchr memcmp strupr #include // atof #include // toupper +#include // floor #include "mapcoder.h" #include "basics.h" @@ -170,12 +171,7 @@ static int disambiguate_str(const char *s, int len) // returns disambiguation 1- if (len != 2 && len != 3) { return -923; } // solve bad args memcpy(country, s, len); country[len] = 0; - { - char *t; - for (t = country; *t != 0; t++) { - *t = (char) toupper(*t); - } - } + strupr(country); f = strstr(p, country); if (f == NULL) { return -23; // unknown country @@ -1357,6 +1353,10 @@ static int decoderEngine(decodeRec *dec) { if (ccode == ccode_mex && len < 8) { ccode = ccode_of_iso3("5MX", -1); } // special case for mexico country vs state + if (*s=='u' || *s=='U') { + strcpy(s,s+1); + repack_if_alldigits(s, 1); + } dec->context = ccode; dec->mapcode = s; dec->extension = ""; @@ -1905,23 +1905,25 @@ int cmp_alphacode(const void *e1, const void *e2) { } // cmp int binfindmatch(int parentcode, const char *str) { + // build a 4-letter uppercase search term char tmp[5]; if (parentcode < 0) { return -1; } if (parentcode > 0) { tmp[0] = '0' + parentcode; memcpy(tmp + 1, str, 3); - tmp[4] = 0; - str = tmp; - } // + } else { + memcpy(tmp, str, 4); + } + tmp[4] = 0; { // binary-search the result const alphaRec *p; alphaRec t; - t.alphaCode = str; + t.alphaCode = strupr(tmp); t.ccode = parentcode; p = (const alphaRec *) bsearch(&t, alphaSearch, NRTERREC, sizeof(alphaRec), cmp_alphacode); if (p) { - if (strcmp(str, p->alphaCode) == 0) { + if (strcmp(t.alphaCode, p->alphaCode) == 0) { // only interested in PERFECT match return p->ccode + 1; } // match } // found From c68a13b4b86d785e23dd7093f8672947f3e75af0 Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Thu, 20 Aug 2015 20:07:12 +0200 Subject: [PATCH 2/2] 2.1.0 Fix floating point error accumulation Prevent encode(decode(M)) != M --- mapcodelib/mapcoder.c | 262 +++++++++++++++++++++++++++++++----------- mapcodelib/mapcoder.h | 6 +- 2 files changed, 196 insertions(+), 72 deletions(-) diff --git a/mapcodelib/mapcoder.c b/mapcodelib/mapcoder.c index 3f1b181..510b510 100644 --- a/mapcodelib/mapcoder.c +++ b/mapcodelib/mapcoder.c @@ -64,8 +64,13 @@ typedef struct { int context; // input territory context (or negative) const char *iso; // input territory alphacode (context) // output - point result; // result - point32 coord32; // result in integer arithmetic (millionts of degrees) + point result; // result + point32 coord32; // result in integer arithmetic (millionts of degrees) +#ifdef FORCE_RECODE + #define MICROMETER (0.000001) // millionth millionth degree ~ 0,1 micron ~ 1/810000th + point range_min; + point range_max; +#endif } decodeRec; @@ -301,8 +306,14 @@ static void encodeExtension(char *result, int extrax4, int extray, int dividerx4 const encodeRec *enc) // append extra characters to result for more precision { char *s = result + strlen(result); - double encx = (extrax4 + 4 * enc->fraclon) / (dividerx4); - double ency = (extray + enc->fraclat * ydirection) / (dividery); + int factorx = MAX_PRECISION_FACTOR * dividerx4; // 30^4 + int factory = MAX_PRECISION_FACTOR * dividery; // 30^4 + int valx = (int) floor(MAX_PRECISION_FACTOR * (extrax4 + 4 * enc->fraclon)); + int valy = (int) floor(MAX_PRECISION_FACTOR * (extray + enc->fraclat * ydirection)); + + // protect against floating point errors + if (valx<0) { valx=0; } else if (valx>=factorx) { valx=factorx-1; } + if (valy<0) { valy=0; } else if (valy>=factory) { valy=factory-1; } if (extraDigits < 0) { extraDigits = 0; } else if (extraDigits > MAX_PRECISION_DIGITS) { extraDigits = MAX_PRECISION_DIGITS; @@ -315,20 +326,14 @@ static void encodeExtension(char *result, int extrax4, int extray, int dividerx4 while (extraDigits-- > 0) { int gx, gy, column1, column2, row1, row2; - encx *= 30; - gx = (int) encx; - if (gx < 0) { - gx = 0; - } else if (gx > 29) { - gx = 29; - } - ency *= 30; - gy = (int) ency; - if (gy < 0) { - gy = 0; - } else if (gy > 29) { - gy = 29; - } + factorx /= 30; + gx = (valx / factorx); + valx -= factorx * gx; + + factory /= 30; + gy = (valy / factory); + valy -= factory * gy; + column1 = (gx / 6); column2 = (gx % 6); row1 = (gy / 5); @@ -339,22 +344,21 @@ static void encodeExtension(char *result, int extrax4, int extray, int dividerx4 *s++ = encode_chars[row2 * 6 + column2]; } *s = 0; - encx -= gx; - ency -= gy; } } #define decodeChar(c) decode_chars[(unsigned char)c] // force c to be in range of the index, between 0 and 255 // this routine takes the integer-arithmeteic decoding results (in millionths of degrees), adds any floating-point precision digits, and returns the result (still in millionths) -static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int ydirection) { +static int decodeExtension(decodeRec *dec, int dividerx4, int dividery0, int lon_offset4) { const char *extrapostfix = dec->extension; - double dividerx = dividerx4 / 4.0, processor = 1.0; - dec->result.lon = 0; - dec->result.lat = 0; + double dividerx = dividerx4 / 4.0, dividery = dividery0; + int lon32 = 0; + int lat32 = 0; + int processor = 1; + int odd = 0; while (*extrapostfix) { int column1, row1, column2, row2; - double halfcolumn = 0; int c1 = *extrapostfix++; c1 = decodeChar(c1); if (c1 < 0 || c1 == 30) { return -1; } // illegal extension character @@ -367,25 +371,39 @@ static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int ydir column2 = (c2 % 6); } else { - row2 = 2; - halfcolumn = 0.5; - column2 = 3; + odd = 1; + row2 = 0; + column2 = 0; } - processor *= 30; - dec->result.lon += ((column1 * 6 + column2)) / processor; - dec->result.lat += ((row1 * 5 + row2 - halfcolumn)) / processor; + processor *= 30; + lon32 = lon32 * 30 + column1 * 6 + column2; + lat32 = lat32 * 30 + row1 * 5 + row2; } - dec->result.lon += (0.5 / processor); - dec->result.lat += (0.5 / processor); - - dec->result.lon *= dividerx; - dec->result.lat *= (dividery * ydirection); + dec->result.lon = dec->coord32.lon + (lon32 * dividerx / processor) + lon_offset4 / 4.0; + dec->result.lat = dec->coord32.lat + (lat32 * dividery / processor); - dec->result.lon += dec->coord32.lon; - dec->result.lat += dec->coord32.lat; +#ifdef FORCE_RECODE + dec->range_min.lon = dec->result.lon; + dec->range_min.lat = dec->result.lat; + if (odd) { + dec->range_max.lon = dec->range_min.lon + (dividerx / (processor / 6)); + dec->range_max.lat = dec->range_min.lat + (dividery / (processor / 5)); + } else { + dec->range_max.lon = dec->range_min.lon + (dividerx / processor); + dec->range_max.lat = dec->range_min.lat + (dividery / processor); + } // +#endif // FORCE_RECODE + if (odd) { + dec->result.lon += dividerx / (2 * processor / 6); + dec->result.lat += dividery / (2 * processor / 5); + } else { + dec->result.lon += (dividerx / (2 * processor)); + dec->result.lat += (dividery / (2 * processor)); + } // not odd + // also convert back to int dec->coord32.lon = (int) floor(dec->result.lon); dec->coord32.lat = (int) floor(dec->result.lat); @@ -597,12 +615,29 @@ static int decodeGrid(decodeRec *dec, int m, int hasHeaderLetter) { dec->coord32.lon = relx + (difx * dividerx); dec->coord32.lat = rely + (dify * dividery); + if (!fitsInside(&dec->coord32,m)) { + return -912; + } { - int err = decodeExtension(dec, dividerx << 2, dividery, 1); // grid + int err = decodeExtension(dec, dividerx << 2, dividery, 0); // grid if (err) { return err; } +#ifdef FORCE_RECODE + if (dec->result.lon >= relx + xgridsize) { + dec->coord32.lon = (int) floor(dec->result.lon = relx + xgridsize - MICROMETER); + } // keep in inner cell + if (dec->result.lat >= rely + ygridsize) { + dec->coord32.lat = (int) floor(dec->result.lat = rely + ygridsize - MICROMETER); + } // keep in inner cell + if (dec->result.lon >= b->maxx) { + dec->coord32.lon = (int) floor(dec->result.lon = b->maxx - MICROMETER); + } // keep in territory + if (dec->result.lat >= b->maxy) { + dec->coord32.lat = (int) floor(dec->result.lat = b->maxy - MICROMETER); + } // keep in territory +#endif // FORCE_RECODE return 0; } @@ -875,7 +910,7 @@ static int decodeNameless(decodeRec *dec, int m) { if (dx >= xSIDE) { - return -123; //EVER? + return -123; } { @@ -887,8 +922,17 @@ static int decodeNameless(decodeRec *dec, int m) { dec->coord32.lon = b->minx + ((dx * dividerx4) / 4); dec->coord32.lat = b->maxy - (dy * dividery); - err = decodeExtension(dec, dividerx4, dividery, -1); // nameless - dec->result.lon += ((dx * dividerx4) % 4) / 4.0; + err = decodeExtension(dec, dividerx4, -dividery, ((dx * dividerx4) % 4)); // nameless + +#ifdef FORCE_RECODE + // keep within outer rect + if (dec->result.lat < b->miny) { + dec->result.lat = (dec->coord32.lat = b->miny); + } // keep in territory + if (dec->result.lon >= b->maxx) { + dec->coord32.lon = (int) floor(dec->result.lon = b->maxx - MICROMETER); + } // keep in territory +#endif return err; } @@ -1157,11 +1201,20 @@ static int decodeAutoHeader(decodeRec *dec, int m) { dec->coord32.lat > b->maxy) // *** CAREFUL! do this test BEFORE adding remainder... { return -122; // invalid code - } + } } } - err = decodeExtension(dec, dividerx << 2, dividery, -1); // autoheader decode + err = decodeExtension(dec, dividerx << 2, -dividery, 0); // autoheader decode + +#ifdef FORCE_RECODE + if (dec->result.lat < b->miny) { + dec->result.lat = (dec->coord32.lat = b->miny); + } // keep in territory + if (dec->result.lon >= b->maxx) { + dec->coord32.lon = (int) floor(dec->result.lon = b->maxx - MICROMETER); + } // keep in territory +#endif return err; } @@ -1447,21 +1500,63 @@ static int decoderEngine(decodeRec *dec) { err = decodeGrid(dec, i, 0); // *** make sure decode fits somewhere *** - if (isRestricted(i)) { + if (err==0 && isRestricted(i)) { int fitssomewhere = 0; int j; for (j = i - 1; j >= from; j--) { // look in previous rects if (!isRestricted(j)) { - if (fitsInsideWithRoom(&dec->coord32, j)) { + if (fitsInside(&dec->coord32, j)) { fitssomewhere = 1; break; } } } +#ifdef FORCE_RECODE + if (err==0 && !fitssomewhere) { + for (j = from; j < i; j++) { // try all smaller rectangles j + if (!isRestricted(j)) { + const mminforec *b = boundaries(j); + int bminx = b->minx; + int bmaxx = b->maxx; + point p = dec->result; + + if (bmaxx < 0 && p.lon > 0) { + bminx += 360000000; + bmaxx += 360000000; + } // + + if (p.lat < b->miny && b->miny <= dec->range_max.lat) { + p.lat = b->miny; + } // keep in range + if (p.lat >= b->maxy && b->maxy > dec->range_min.lat) { + p.lat = b->maxy - MICROMETER; + } // keep in range + if (p.lon < bminx && bminx <= dec->range_max.lon) { + p.lon = bminx; + } // keep in range + if (p.lon >= bmaxx && bmaxx > dec->range_min.lon) { + p.lon = bmaxx - MICROMETER; + } // keep in range + + if ( p.lat > dec->range_min.lat && p.lat < dec->range_max.lat && + p.lon > dec->range_min.lon && p.lon < dec->range_max.lon && + b->miny <= p.lat && p.lat < b->maxy && + bminx <= p.lon && p.lon < bmaxx ) { + + dec->result = p; + dec->coord32.lat = (int) floor(p.lat); + dec->coord32.lon = (int) floor(p.lon); + fitssomewhere = 1; + break; + } // candidate + } // isRestricted + } // for j + } +#endif //FORCE_RECODE if (!fitssomewhere) { err = -1234; } - } // *** make sure decode fits somewhere *** + } // *** make sure decode fits somewhere *** break; } } @@ -1507,8 +1602,36 @@ static int decoderEngine(decodeRec *dec) { if (err == 0) { if (ccode != ccode_earth) { if (!fitsInsideWithRoom(&dec->coord32, lastrec(ccode))) { - err = -2222; // EVER? + err = -2222; } +#ifdef FORCE_RECODE + else { + const mminforec *b = boundaries(lastrec(ccode)); + int bminx = b->minx; + int bmaxx = b->maxx; + if (dec->coord32.lon < 0 && bminx > 0) { + bminx -= 360000000; + bmaxx -= 360000000; + } + + if (dec->result.lat < b->miny / 1000000.0) { + dec->result.lat = b->miny / 1000000.0; + } // keep in encompassing territory + if (dec->result.lat >= b->maxy / 1000000.0) { + dec->result.lat = (b->maxy - MICROMETER) / 1000000.0; + } // keep in encompassing territory + + if (dec->result.lon < bminx / 1000000.0) { + dec->result.lon = bminx / 1000000.0; + } // keep in encompassing territory + if (dec->result.lon >= bmaxx / 1000000.0) { + dec->result.lon = (bmaxx - MICROMETER) / 1000000.0; + } // keep in encompassing territory + + dec->coord32.lat = (int) floor(dec->result.lat * 1000000); + dec->coord32.lon = (int) floor(dec->result.lon * 1000000); + } // FORCE_RECODE +#endif } } @@ -1764,37 +1887,36 @@ static int encodeLatLonToMapcodes_internal(char **v, Mapcodes *mapcodes, double enc.mapcodes->count = 0; if (lat < -90) { lat = -90; } else if (lat > 90) { lat = 90; } - if (lon < -180 || lon > 180) { - lon -= (360.0 * floor(lon / 360)); - if (lon >= 180) { lon -= 360; } - } + lat += 90; // lat now [0..180] + lon -= (360.0 * floor(lon / 360)); // lon now in [0..360> - lat += 90; - lon += 180; lat *= 1000000; lon *= 1000000; enc.coord32.lat = (int) lat; enc.coord32.lon = (int) lon; enc.fraclat = lat - enc.coord32.lat; enc.fraclon = lon - enc.coord32.lon; - // for 8-digit precision, cells are divided into 810,000 by 810,000 minicells. - enc.fraclat *= 810000; - if (enc.fraclat < 1) { enc.fraclat = 0; } else { - if (enc.fraclat > 809999) { - enc.fraclat = 0; - enc.coord32.lat++; - } else { enc.fraclat /= 810000; } - } - enc.fraclon *= 810000; - if (enc.fraclon < 1) { enc.fraclon = 0; } else { - if (enc.fraclon > 809999) { - enc.fraclon = 0; - enc.coord32.lon++; - } else { enc.fraclon /= 810000; } + { + double f; + // for 8-digit precision, cells are divided into 810,000 by 810,000 minicells. + f = enc.fraclat * MAX_PRECISION_FACTOR; + if (f < 1) { enc.fraclat = 0; } else { + if (f >= (MAX_PRECISION_FACTOR - 0.5)) { + enc.fraclat = 0; + enc.coord32.lat++; + } + } + f = enc.fraclon * MAX_PRECISION_FACTOR; + if (f < 1) { enc.fraclon = 0; } else { + if (f >= (MAX_PRECISION_FACTOR - 0.5)) { + enc.fraclon = 0; + enc.coord32.lon++; + } + } } enc.coord32.lat -= 90000000; - enc.coord32.lon %= 360000000; - enc.coord32.lon -= 180000000; + if (enc.coord32.lon >= 180000000) + enc.coord32.lon -= 360000000; if (tc <= 0) // ALL results? { diff --git a/mapcodelib/mapcoder.h b/mapcodelib/mapcoder.h index ecafee0..90f5b4b 100644 --- a/mapcodelib/mapcoder.h +++ b/mapcodelib/mapcoder.h @@ -18,16 +18,18 @@ extern "C" { #endif -#define mapcode_cversion "2.0.3" +#define mapcode_cversion "2.1.0" #define UWORD unsigned short int // 2-byte unsigned integer. #define SUPPORT_FOREIGN_ALPHABETS // Define to support additional alphabets. #define SUPPORT_HIGH_PRECISION // Define to enable high-precision extension logic. +#define FORCE_RECODE // Define to enforce that encode(decode(M)) generates M #define MAX_NR_OF_MAPCODE_RESULTS 21 // Max. number of results ever returned by encoder (e.g. for 26.904899, 95.138515). #define MAX_PROPER_MAPCODE_LEN 10 // Max. number of characters in a proper mapcode (including the dot). -#define MAX_PRECISION_DIGITS 8 // Max. number of extension characters (excluding the hyphen). +#define MAX_PRECISION_DIGITS 8 // Max. number of extension characters (excluding the hyphen). Must be even. +#define MAX_PRECISION_FACTOR 810000 // 30 to the power (MAX_PRECISION_DIGITS/2) #define MAX_ISOCODE_LEN 7 // Max. number of characters of a valid territory code; although nothing longer than SIX characters is ever generated (RU-KAM), users can input SEVEN characters (RUS-KAM). #define MAX_CLEAN_MAPCODE_LEN (MAX_PROPER_MAPCODE_LEN + 1 + MAX_PRECISION_DIGITS) // Max. number of characters in a clean mapcode (excluding zero-terminator). #define MAX_MAPCODE_RESULT_LEN (MAX_ISOCODE_LEN + 1 + MAX_CLEAN_MAPCODE_LEN + 1) // Max. number of characters to store a single result (including zero-terminator).