From ef96f1a03b195482fbb93381d572663ccef3b488 Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Mon, 7 Sep 2015 22:35:38 +0200 Subject: [PATCH 1/6] Prettified and re-grouped C source --- mapcodelib/mapcode_fastalpha.h | 1 + mapcodelib/mapcoder.c | 2428 +++++++++++++++----------------- mapcodelib/mapcoder.h | 20 +- unitttest/unittest.c | 177 +-- 4 files changed, 1244 insertions(+), 1382 deletions(-) diff --git a/mapcodelib/mapcode_fastalpha.h b/mapcodelib/mapcode_fastalpha.h index 5ce3495..5ef3e0b 100644 --- a/mapcodelib/mapcode_fastalpha.h +++ b/mapcodelib/mapcode_fastalpha.h @@ -14,6 +14,7 @@ * limitations under the License. */ +// *** GENERATED FILE, DO NOT CHANGE OR PRETTIFY *** static const char parentletter[MAX_CCODE + 1] = { diff --git a/mapcodelib/mapcoder.c b/mapcodelib/mapcoder.c index 6e8489e..7f5bf56 100644 --- a/mapcodelib/mapcoder.c +++ b/mapcodelib/mapcoder.c @@ -20,22 +20,77 @@ #include // floor #include "mapcoder.h" #include "basics.h" +#include "mapcode_fastalpha.h" #define FAST_ENCODE #ifdef FAST_ENCODE #include "mapcode_fast_encode.h" #endif -#define FAST_ALPHA -#ifdef FAST_ALPHA -#include "mapcode_fastalpha.h" -#endif +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// distanceInMeters +// +/////////////////////////////////////////////////////////////////////////////////////////////// + +// PUBLIC - returns distance (in meters) between two coordinates (in degrees) +double distanceInMeters(double latDeg1, double lonDeg1, double latDeg2, double lonDeg2) { + // Radius of Earth. + #define EARTH_RADIUS_X_METERS 6378137 + #define EARTH_RADIUS_Y_METERS 6356752 + + // Circumference of Earth. + #define EARTH_CIRCUMFERENCE_X (EARTH_RADIUS_X_METERS * 2 * _PI) + #define EARTH_CIRCUMFERENCE_Y (EARTH_RADIUS_Y_METERS * 2 * _PI) + + // Meters per degree latitude is fixed. For longitude: use factor * cos(midpoint of two degree latitudes). + #define METERS_PER_DEGREE_LAT (EARTH_CIRCUMFERENCE_Y / 360.0) + #define METERS_PER_DEGREE_LON (EARTH_CIRCUMFERENCE_X / 360.0) + + // PI + #define _PI 3.14159265358979323846 + + if (lonDeg1 < 0 && lonDeg2 > 1) { lonDeg1 += 360; } + if (lonDeg2 < 0 && lonDeg1 > 1) { lonDeg2 += 360; } + { + const double dy = (latDeg2 - latDeg1) * METERS_PER_DEGREE_LAT; + const double dx = (lonDeg2 - lonDeg1) * METERS_PER_DEGREE_LON * cos((latDeg1 + latDeg2) * _PI / 360.0); + return sqrt(dx * dx + dy * dy); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// maxErrorInMeters +// +/////////////////////////////////////////////////////////////////////////////////////////////// + +// maximum error in meters for a certain nr of high-precision digits +static const double maxErrorInMetersForDigits[MAX_PRECISION_DIGITS + 1] = { + 7.49, + 1.39, + 0.251, + 0.0462, + 0.00837, + 0.00154, + 0.00028, + 0.000052, + 0.0000093 +}; + +// PUBLIC - returns maximum error in meters for a certain nr of high-precision digits +double maxErrorInMeters(int extraDigits) { + if ((extraDigits < 0) || (extraDigits > MAX_PRECISION_DIGITS)) { + return (double) 0; + } + return maxErrorInMetersForDigits[extraDigits]; +} -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// // -// Structures +// point / point32 // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// typedef struct { int lat; // latitude in microdegrees @@ -43,28 +98,102 @@ typedef struct { } point32; typedef struct { // point - double lat; // latitude in degrees - double lon; // longitude in degrees + double lat; // latitude (units depend on situation) + double lon; // longitude (units depend on situation) } point; -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +static point32 convertFractionsToCoord32(const point *p) { + point32 p32; + p32.lat = (int) floor(p->lat / 810000); + p32.lon = (int) floor(p->lon / 3240000); + return p32; +} + +static point convertFractionsToDegrees(const point *p) { + point pd; + pd.lat = p->lat / ( 810000 * 1000000.0); + pd.lon = p->lon / (3240000 * 1000000.0); + return pd; +} + +static void convertCoordsToMicrosAndFractions(point32 *coord32, int *fraclat, int *fraclon, double lat, double lon) { + double frac; + if (lat < -90) { lat = -90; } else if (lat > 90) { lat = 90; } + lat += 90; // lat now [0..180] + lat *= (double) 810000000000; + frac = floor(lat + 0.1); + coord32->lat = (int) (frac / (double) 810000); + if (fraclat) { + frac -= ((double) coord32->lat * (double) 810000); + *fraclat = (int) frac; + } + coord32->lat -= 90000000; + + lon -= (360.0 * floor(lon / 360)); // lon now in [0..360> + lon *= (double) 3240000000000; + frac = floor(lon + 0.1); + coord32->lon = (int) (frac / (double) 3240000); + if (fraclon) { + frac -= (double) coord32->lon * (double) 3240000; + *fraclon = (int) frac; + } + if (coord32->lon >= 180000000) { + coord32->lon -= 360000000; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// Boundaries (specified in microDegrees) +// +/////////////////////////////////////////////////////////////////////////////////////////////// + +#define Boundaries mminforec + +// returns nonzero if x in the range minx...maxx +static int isInRange(int x, const int minx, const int maxx) +{ + if (minx <= x && x < maxx) { return 1; } + if (x < minx) { x += 360000000; } else { x -= 360000000; } // 1.32 fix FIJI edge case + if (minx <= x && x < maxx) { return 1; } + return 0; +} + +// returns true iff given coordinate "coord32" fits inside given Boundaries +static int fitsInsideBoundaries(const point32 *coord32, const Boundaries *b) { + return (b->miny <= coord32->lat && coord32->lat < b->maxy && isInRange(coord32->lon, b->minx, b->maxx)); +} + +// set target Boundaries to a source extended with deltalat, deltaLon (in microDegrees) +static Boundaries *getExtendedBoundaries(Boundaries *target, const Boundaries *source, + const int deltaLat, const int deltaLon) { + target->miny = source->miny - deltaLat; + target->minx = source->minx - deltaLon; + target->maxy = source->maxy + deltaLat; + target->maxx = source->maxx + deltaLon; + return target; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// // // MapcodeZone // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// #define MICROLAT_TO_FRACTIONS_FACTOR ((double)MAX_PRECISION_FACTOR) #define MICROLON_TO_FRACTIONS_FACTOR (4.0 * MAX_PRECISION_FACTOR) typedef struct { // latitudes in "810 billionths", range [-729 E11 .. +720 E11), is well within (-2^47 ... +2^47) - double fminy; + double fminy; double fmaxy; // latitudes in "3240 billionths", range [-2916 E13 .. +2916 E13), is well within (-2^49 ... +2^49) double fminx; - double fmaxx; + double fmaxx; } MapcodeZone; -void setFromFractions(MapcodeZone *z, double y, double x, double yDelta, double xDelta) { +static void setFromFractions(MapcodeZone *z, + const double y, const double x, + const double yDelta, const double xDelta) { z->fminx = x; z->fmaxx = x + xDelta; if (yDelta < 0) { @@ -77,105 +206,79 @@ void setFromFractions(MapcodeZone *z, double y, double x, double yDelta, double } } -int isEmpty(const MapcodeZone *z) { +static int isEmpty(const MapcodeZone *z) { return ((z->fmaxx <= z->fminx) || (z->fmaxy <= z->fminy)); } -point getMidPointFractions(MapcodeZone *z) { +static point getMidPointFractions(const MapcodeZone *z) { point p; p.lon = floor((z->fminx + z->fmaxx) / 2); p.lat = floor((z->fminy + z->fmaxy) / 2); return p; } -point32 convertFractionsToCoord32(const point *p) { - point32 p32; - p32.lat = (int) floor(p->lat / 810000); - p32.lon = (int) floor(p->lon / 3240000); - return p32; -} - -point convertFractionsToDegrees(const point *p) { - point pd; - pd.lat = p->lat / ( 810000 * 1000000.0); - pd.lon = p->lon / (3240000 * 1000000.0); - return pd; -} - -void zoneCopyFrom(MapcodeZone *target, const MapcodeZone *source) { - target->fminy = source->fminy; +static void zoneCopyFrom(MapcodeZone *target, const MapcodeZone *source) { + target->fminy = source->fminy; target->fmaxy = source->fmaxy; target->fminx = source->fminx; - target->fmaxx = source->fmaxx; + target->fmaxx = source->fmaxx; } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Structures -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -typedef struct { - // input - point32 coord32; - int fraclat; // latitude fraction of microdegrees, expressed in 1 / 810,000ths - int fraclon; // longitude fraction of microdegrees, expressed in 1 / 3,240,000ths - // output - Mapcodes *mapcodes; -} encodeRec; - -typedef struct { - // input - const char *orginput; // original full input string - char minput[MAX_MAPCODE_RESULT_LEN]; // room to manipulate clean copy of input - const char *mapcode; // input mapcode (first character of proper mapcode excluding territory code) - const char *extension; // input extension (or empty) - int context; // input territory context (or negative) - const char *iso; // input territory alphacode (context) - // output - point result; // result - point32 coord32; // result in integer arithmetic (microdegrees) - MapcodeZone zone; // result zone (in "DegreeFractions") -} decodeRec; - +// determine the non-empty intersection zone z between a given zone and the boundaries of territory rectangle m. +// returns nonzero in case such a zone exists +static int restrictZoneTo(MapcodeZone *z, const MapcodeZone *zone, const Boundaries *b) { + z->fminy = zone->fminy; + z->fmaxy = zone->fmaxy; + if (z->fminy < b->miny * MICROLAT_TO_FRACTIONS_FACTOR) { + z->fminy = b->miny * MICROLAT_TO_FRACTIONS_FACTOR; + } + if (z->fmaxy > b->maxy * MICROLAT_TO_FRACTIONS_FACTOR) { + z->fmaxy = b->maxy * MICROLAT_TO_FRACTIONS_FACTOR; + } + if (z->fminy < z->fmaxy) { + double bminx = b->minx * MICROLON_TO_FRACTIONS_FACTOR; + double bmaxx = b->maxx * MICROLON_TO_FRACTIONS_FACTOR; + z->fminx = zone->fminx; + z->fmaxx = zone->fmaxx; + if (bmaxx < 0 && z->fminx > 0) { + bminx += (360000000 * MICROLON_TO_FRACTIONS_FACTOR); + bmaxx += (360000000 * MICROLON_TO_FRACTIONS_FACTOR); + } + else if (bminx > 0 && z->fmaxx < 0) { + bminx -= (360000000 * MICROLON_TO_FRACTIONS_FACTOR); + bmaxx -= (360000000 * MICROLON_TO_FRACTIONS_FACTOR); + } + if (z->fminx < bminx) { + z->fminx = bminx; + } + if (z->fmaxx > bmaxx) { + z->fmaxx = bmaxx; + } + return (z->fminx < z->fmaxx); + } + return 0; +} -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// // // Data access // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// + +/*** low-level data access ***/ static int firstrec(const int ccode) { return data_start[ccode]; } static int lastrec(const int ccode) { return data_start[ccode + 1] - 1; } -#ifdef FAST_ALPHA #define ParentLetter(ccode) ((int)parentletter[ccode]) -#else - -static int ParentLetter(const int ccode) // returns parent index (>0), or 0 -{ - if (ccode >= usa_from && ccode <= usa_upto) { return 1; } - if (ccode >= ind_from && ccode <= ind_upto) { return 2; } - if (ccode >= can_from && ccode <= can_upto) { return 3; } - if (ccode >= aus_from && ccode <= aus_upto) { return 4; } - if (ccode >= mex_from && ccode <= mex_upto) { return 5; } - if (ccode >= bra_from && ccode <= bra_upto) { return 6; } - if (ccode >= rus_from && ccode <= rus_upto) { return 7; } - if (ccode >= chn_from && ccode <= chn_upto) { return 8; } - return 0; -} - -#endif -static int ParentTerritoryOf(const int ccode) // returns parent, or -1 -{ +// returns parent of ccode, or -1 +static int ParentTerritoryOf(const int ccode) { return parentnr[ParentLetter(ccode)]; } -static int isSubdivision(const int ccode) { return (ParentTerritoryOf(ccode) >= 0); } - -static int coDex(int m) { +static int coDex(const int m) { int c = mminfo[m].flags & 31; return 10 * (c / 5) + ((c % 5) + 1); } @@ -190,22 +293,6 @@ static int coDex(int m) { #define smartDiv(m) (mminfo[m].flags>>16) #define boundaries(m) (&mminfo[m]) -static int isInRange(int x, const int minx, int const maxx) // returns nonzero if x in the range minx...maxx -{ - if (minx <= x && x < maxx) { return 1; } - if (x < minx) { x += 360000000; } else { x -= 360000000; } // 1.32 fix FIJI edge case - if (minx <= x && x < maxx) { return 1; } - return 0; -} - -static int fitsInsideBoundaries(const point32 *coord32, const mminforec *b) { - return (b->miny <= coord32->lat && coord32->lat < b->maxy && isInRange(coord32->lon, b->minx, b->maxx)); -} - -static int fitsInside(const point32 *coord32, const int m) { - return fitsInsideBoundaries(coord32,boundaries(m)); -} - static int xDivider4(const int miny, const int maxy) { if (miny >= 0) { // both above equator? then miny is closest return xdivider19[(miny) >> 19]; @@ -216,41 +303,55 @@ static int xDivider4(const int miny, const int maxy) { return xdivider19[(-maxy) >> 19]; // both negative, so maxy is closest to equator } -static mminforec *getExtendedBoundaries(mminforec *target, const mminforec *source, int deltaLat, int deltaLon) { - target->miny = source->miny - deltaLat; - target->minx = source->minx - deltaLon; - target->maxy = source->maxy + deltaLat; - target->maxx = source->maxx + deltaLon; - return target; +// Legacy: NOT threadsafe +static int debugStopAt = -1; // to externally test-restrict internal encoding, do not use! + +#define decodeChar(c) decode_chars[(unsigned char)c] // force c to be in range of the index, between 0 and 255 + +/*** mid-level data access ***/ + +// returns true iff ccode is a subdivision of some other country +static int isSubdivision(const int ccode) { + return (ParentTerritoryOf(ccode) >= 0); } -static int isNearBorderOf(const point32 *coord32, int m) { - mminforec tmp; - const mminforec *b=boundaries(m); - int xdiv8 = xDivider4(b->miny, b->maxy) / 4; // should be /8 but there's some extra margin - return (fitsInsideBoundaries(coord32, getExtendedBoundaries(&tmp,boundaries(m),+60,+xdiv8)) && - (! fitsInsideBoundaries(coord32, getExtendedBoundaries(&tmp,boundaries(m),-60,-xdiv8)))); +// find first territory rectangle of the same type as m +static int firstNamelessRecord(const int m, const int firstcode) { + int i = m; + const int codexm = coDex(m); + while (i >= firstcode && coDex(i) == codexm && isNameless(i)) { i--; } + return (i + 1); } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// Lowlevel ccode, iso, and disambiguation -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// count all territory rectangles of the same type as m +static int countNamelessRecords(const int m, const int firstcode) { + const int first = firstNamelessRecord(m, firstcode); + const int codexm = coDex(m); + int last = m; + while (coDex(last) == codexm) { last++; } + return (last - first); +} -static const char *get_entity_iso3(char *entity_iso3_result, int ccode) { - if (ccode < 0 || ccode >= MAX_MAPCODE_TERRITORY_CODE) { ccode = ccode_earth; } // solve bad args +static int isNearBorderOf(const point32 *coord32, const Boundaries *b) { + int xdiv8 = xDivider4(b->miny, b->maxy) / 4; // should be /8 but there's some extra margin + Boundaries tmp; + return (fitsInsideBoundaries(coord32, getExtendedBoundaries(&tmp, b, +60, +xdiv8)) && + (!fitsInsideBoundaries(coord32, getExtendedBoundaries(&tmp, b, -60, -xdiv8)))); +} + +static const char *get_entity_iso3(char *entity_iso3_result, const int ccode) { + if (ccode < 0 || ccode >= MAX_MAPCODE_TERRITORY_CODE) { return "AAA"; } // solve bad args memcpy(entity_iso3_result, entity_iso + ccode * 4, 3); entity_iso3_result[3] = 0; return entity_iso3_result; } -static void makeupper(char *s) -{ - while(*s) { *s = (char) toupper(*s); s++; } +static void makeupper(char *s) { + while (*s) { *s++ = (char) toupper(*s); } } -static int disambiguate_str(const char *s, const int len) // returns disambiguation 1-8, or negative if error +// returns 1 - 8, or negative if error +static int getParentcode(const char *s, const int len) { const char *p = (len == 2 ? parents2 : parents3); const char *f; @@ -267,182 +368,681 @@ static int disambiguate_str(const char *s, const int len) // returns disambiguat return 1 + (int) ((f - p) / (len + 1)); } +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// MAPCODE ALL-DIGIT PACKING/UNPACKING +// +/////////////////////////////////////////////////////////////////////////////////////////////// -#ifndef FAST_ALPHA -// returns coode, or negative if invalid -static int ccode_of_iso3(const char *in_iso, int parentcode) { - const char *aliases = ALIASES; - char iso[4]; - const char *s; - int hyphenated = 0; - - if (in_iso && in_iso[0] && in_iso[1]) { - if (in_iso[2]) { - if (in_iso[2] == '-') { - parentcode = disambiguate_str(in_iso, 2); - if (parentcode < 0) { return parentcode; } - hyphenated = 1; - in_iso += 3; - } - else if (in_iso[3] == '-') { - parentcode = disambiguate_str(in_iso, 3); - if (parentcode < 0) { return parentcode; } - hyphenated = 1; - in_iso += 4; +static void repack_if_alldigits(char *input, const int aonly) { + char *s = input; + int alldigits = 1; // assume all digits + char *e; + char *dotpos = NULL; + + for (e = s; *e != 0 && *e != '-'; e++) { + if (*e < '0' || *e > '9') { + if (*e == '.' && !dotpos) { + dotpos = e; + } else { + alldigits = 0; + break; } } - } else { return -23; } // solve bad args - - // make (uppercased) copy of at most three characters - iso[0] = (char) toupper(in_iso[0]); - if (iso[0]) { iso[1] = (char) toupper(in_iso[1]); } - if (iso[1]) { iso[2] = (char) toupper(in_iso[2]); } - if (iso[2] && in_iso[3]!=0 && in_iso[3]!='-') { return -41; } - iso[3] = 0; - - if (iso[2] == 0 || iso[2] == ' ') // 2-letter iso code? + } + e--; + s = e - 1; + if (alldigits && dotpos && + s > dotpos) // e is last char, s is one before, both are beyond dot, all characters are digits { - static char disambiguate_iso3[4] = {'0', '?', '?', 0}; // cache for disambiguation - if (parentcode > 0) { - disambiguate_iso3[0] = (char) ('0' + parentcode); - } - disambiguate_iso3[1] = iso[0]; - disambiguate_iso3[2] = iso[1]; - - s = strstr(entity_iso, disambiguate_iso3); // search disambiguated 2-letter iso - if (s == NULL) { - s = strstr(aliases, disambiguate_iso3); // search in aliases - if (s == NULL || s[3] != '=') { - s = NULL; - if (disambiguate_iso3[0] <= '9' && !hyphenated) { - disambiguate_iso3[0] = '0'; - s = strstr(aliases, disambiguate_iso3); // search in aliases - } - } - if (s && s[3] == '=') { - memcpy(iso, s + 4, 3); - s = strstr(entity_iso, iso); // search disambiguated 2-letter iso - } + if (aonly) // v1.50 - encode only using the letter A + { + const int v = ((*input) - '0') * 100 + ((*s) - '0') * 10 + ((*e) - '0'); + *input = 'A'; + *s = encode_chars[v / 32]; + *e = encode_chars[v % 32]; } - if (s == NULL && !hyphenated) { - // find the FIRST disambiguation option, if any - for (s = entity_iso - 1; ;) { - s = strstr(s + 1, disambiguate_iso3 + 1); - if (s == NULL) { - break; - } - if (s && s[-1] >= '1' && s[-1] <= '9') { - s--; - break; - } - } - if (s == NULL) { - // find first disambiguation option in aliases, if any - for (s = aliases - 1; ;) { - s = strstr(s + 1, disambiguate_iso3 + 1); - if (s == NULL) { - break; - } - if (s && s[-1] >= '1' && s[-1] <= '9') { - memcpy(iso, s + 3, 3); - s = strstr(entity_iso, iso); // search disambiguated 2-letter iso - break; - } - } - } - - if (s == NULL) { - return -26; - } + else // encode using A,E,U + { + const int v = ((*s) - '0') * 10 + ((*e) - '0'); + *s = encode_chars[(v / 34) + 31]; + *e = encode_chars[v % 34]; } } - else { - s = strstr(entity_iso, iso); // search 3-letter iso - if (s == NULL || hyphenated) { - const char *a = aliases; - while (a) { - a = strstr(a, iso); // search in aliases - if (a && a[3] == '=' && (a[4] > '9' || a[4] == (char) (48 + parentcode) || parentcode < 0)) { - memcpy(iso, a + 4, 3); - a = NULL; - s = strstr(entity_iso, iso); - } else { - if (a) { - a++; - } - } - } +} + +// rewrite all-digit codes +// returns 1 if unpacked, 0 if left unchanged, negative if unchanged and an error was detected +static int unpack_if_alldigits(char *input) { + char *s = input; + char *dotpos = NULL; + const int aonly = ((*s == 'A') || (*s == 'a')); + if (aonly) { s++; } //*** v1.50 + for (; *s != 0 && s[2] != 0 && s[2] != '-'; s++) { + if (*s == '-') { + break; + } else if (*s == '.' && !dotpos) { + dotpos = s; + } else if ((decodeChar(*s) < 0) || (decodeChar(*s) > 9)) { + return 0; + } // nondigit, so stop + } + + if (dotpos) { + if (aonly) // v1.50 encoded only with A's + { + const int v = (((s[0] == 'A') || (s[0] == 'a')) ? 31 : decodeChar(s[0])) * 32 + + (((s[1] == 'A') || (s[1] == 'a')) ? 31 : decodeChar(s[1])); + *input = (char) ('0' + (v / 100)); + s[0] = (char) ('0' + ((v / 10) % 10)); + s[1] = (char) ('0' + (v % 10)); + return 1; + } // v1.50 + + if ((*s == 'a') || (*s == 'e') || (*s == 'u') || + (*s == 'A') || (*s == 'E') || (*s == 'U')) + { + char *e = s + 1; // s is vowel, e is lastchar + + int v = 0; + if (*s == 'e' || *s == 'E') { + v = 34; + } else if (*s == 'u' || *s == 'U') { v = 68; } + + if ((*e == 'a') || (*e == 'A')) { + v += 31; + } else if ((*e == 'e') || (*e == 'E')) { + v += 32; + } else if ((*e == 'u') || (*e == 'U')) { + v += 33; + } else if (decodeChar(*e) < 0) { + return -9; // invalid last character! + } else { v += decodeChar(*e); } + + if (v < 100) { + *s = encode_chars[(unsigned int) v / 10]; + *e = encode_chars[(unsigned int) v % 10]; + } else { + return -31; // overflow (ending in UE or UU) + } + return 1; + } + } + return 0; // no vowel just before end +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// DECODING +// +/////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + // input + point32 coord32; + int fraclat; // latitude fraction of microdegrees, expressed in 1 / 810,000ths + int fraclon; // longitude fraction of microdegrees, expressed in 1 / 3,240,000ths + // output + Mapcodes *mapcodes; +} encodeRec; + +// encode the high-precision extension (0-8 characters) +static void encodeExtension(char *result, const int extrax4, const int extray, const int dividerx4, + const int dividery, int extraDigits, const int ydirection, + const encodeRec *enc) // append extra characters to result for more precision +{ + if (extraDigits > 0) { // anything to do? + char *s = result + strlen(result); + double factorx = (double) MAX_PRECISION_FACTOR * dividerx4; // perfect integer! + double factory = (double) MAX_PRECISION_FACTOR * dividery; // perfect integer! + double valx = ((double) MAX_PRECISION_FACTOR * extrax4) + enc->fraclon; // perfect integer! + double valy = ((double) MAX_PRECISION_FACTOR * extray ) + (ydirection * enc->fraclat); // perfect integer! + + // 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 > MAX_PRECISION_DIGITS) { + extraDigits = MAX_PRECISION_DIGITS; + } + + *s++ = '-'; + + for (; ;) { + int gx, gy; + + factorx /= 30; + gx = (int) (valx / factorx); + + factory /= 30; + gy = (int) (valy / factory); + + *s++ = encode_chars[(gy / 5) * 5 + (gx / 6)]; + if (--extraDigits == 0) { break; } + + *s++ = encode_chars[(gy % 5) * 6 + (gx % 6)]; + if (--extraDigits == 0) { break; } + + valx -= factorx * gx; // for next iteration + valy -= factory * gy; // for next iteration + } + *s = 0; // terminate the result + } +} + +// encode 'value' into result[nrchars] +static void encodeBase31(char *result, int value, int nrchars) { + result[nrchars] = 0; // zero-terminate! + while (nrchars-- > 0) { + result[nrchars] = encode_chars[value % 31]; + value /= 31; + } +} + +static void encode_triple(char *result, const int difx, const int dify) { + if (dify < 4 * 34) // first 4(x34) rows of 6(x28) wide + { + *result = encode_chars[((difx / 28) + 6 * (dify / 34))]; + encodeBase31(result + 1, ((difx % 28) * 34 + (dify % 34)), 2); + } + else // bottom row + { + *result = encode_chars[(difx / 24) + 24]; + encodeBase31(result + 1, (difx % 24) * 40 + (dify - 136), 2); + } +} // encode_triple + +static int encodeSixWide(int x, int y, int width, int height) { + int v; + int D = 6; + int col = x / 6; + const int maxcol = (width - 4) / 6; + if (col >= maxcol) { + col = maxcol; + D = width - maxcol * 6; + } + v = (height * 6 * col) + (height - 1 - y) * D + (x - col * 6); + return v; +} + +// *** mid-level encode routines *** + +// returns *result==0 in case of error +static void encodeGrid(char *result, const encodeRec *enc, const int m, const int extraDigits, const char headerLetter) { + const Boundaries *b = boundaries(m); + + const int orgcodex = coDex(m); + int codexm = orgcodex; + if (codexm == 21) { + codexm = 22; + } + else if (codexm == 14) { + codexm = 23; + } + + *result = 0; + if (headerLetter) { result++; } + + { // encode + int divx, divy; + const int prelen = codexm / 10; + const int postlen = codexm % 10; + + divy = smartDiv(m); + if (divy == 1) { + divx = xside[prelen]; + divy = yside[prelen]; + } + else { + divx = (nc[prelen] / divy); + } + + { // grid + const int ygridsize = (b->maxy - b->miny + divy - 1) / divy; + const int xgridsize = (b->maxx - b->minx + divx - 1) / divx; + int rely = enc->coord32.lat - b->miny; + int x = enc->coord32.lon; + int relx = x - b->minx; + + if (relx < 0) { + relx += 360000000; + x += 360000000; + } + else if (relx >= 360000000) // 1.32 fix FIJI edge case + { + relx -= 360000000; + x -= 360000000; + } + + rely /= ygridsize; + relx /= xgridsize; + + if (relx >= divx || rely >= divy) { + return; + } + + { // prefix + int v; + if (divx != divy && prelen > 2) { + v = encodeSixWide(relx, rely, divx, divy); + } else { + v = relx * divy + (divy - 1 - rely); + } + encodeBase31(result, v, prelen); + } // prefix + + if (prelen == 4 && divx == 961 && divy == 961) { + const char t = result[1]; + result[1] = result[2]; + result[2] = t; + } + + rely = b->miny + (rely * ygridsize); + relx = b->minx + (relx * xgridsize); + + { // postfix + const int dividery = ((ygridsize + yside[postlen] - 1) / yside[postlen]); + const int dividerx = ((xgridsize + xside[postlen] - 1) / xside[postlen]); + int extrax, extray; + + { + char *resultptr = result + prelen; + + + int difx = x - relx; + int dify = enc->coord32.lat - rely; + + *resultptr++ = '.'; + + extrax = difx % dividerx; + extray = dify % dividery; + difx /= dividerx; + dify /= dividery; + + + // reverse y-direction + dify = yside[postlen] - 1 - dify; + + if (postlen == 3) // encode special + { + encode_triple(resultptr, difx, dify); + } + else { + encodeBase31(resultptr, (difx) * yside[postlen] + dify, postlen); + // swap 4-int codes for readability + if (postlen == 4) { + char t = resultptr[1]; + resultptr[1] = resultptr[2]; + resultptr[2] = t; + } + } + } + + if (orgcodex == 14) { + result[2] = result[1]; + result[1] = '.'; + } + + encodeExtension(result, extrax << 2, extray, dividerx << 2, dividery, extraDigits, 1, enc); // grid + if (headerLetter) { + result--; + *result = headerLetter; + } + } // postfix + } // grid + } // encode +} + +// *result==0 in case of error +static void encodeNameless(char *result, const encodeRec *enc, const int input_ctry, + const int extraDigits, const int m) { + // determine how many nameless records there are (A), and which one is this (X)... + const int A = countNamelessRecords(m, firstrec(input_ctry)); + const int X = m - firstNamelessRecord(m, firstrec(input_ctry)); + + *result = 0; + + { + const int p = 31 / A; + const int r = 31 % A; // the first r items are p+1 + const int codexm = coDex(m); + const int codexlen = (codexm / 10) + (codexm % 10); + // determine side of square around centre + int SIDE; + + int storage_offset; + const Boundaries *b; + + int xSIDE, orgSIDE; + + if (codexm != 21 && A <= 31) { + storage_offset = (X * p + (X < r ? X : r)) * (961 * 961); + } + else if (codexm != 21 && A < 62) { + if (X < (62 - A)) { + storage_offset = X * (961 * 961); + } + else { + storage_offset = (62 - A + ((X - 62 + A) / 2)) * (961 * 961); + if ((X + A) & 1) { + storage_offset += (16 * 961 * 31); + } + } + } + else { + const int BASEPOWER = (codexm == 21) ? 961 * 961 : 961 * 961 * 31; + int BASEPOWERA = (BASEPOWER / A); + if (A == 62) { + BASEPOWERA++; + } else { + BASEPOWERA = (961) * (BASEPOWERA / 961); + } + + storage_offset = X * BASEPOWERA; + } + + SIDE = smartDiv(m); + + b = boundaries(m); + orgSIDE = xSIDE = SIDE; + + { + int v = storage_offset; + + const int dividerx4 = xDivider4(b->miny, b->maxy); // *** note: dividerx4 is 4 times too large! + const int xFracture = (enc->fraclon / MAX_PRECISION_FACTOR); + const int dx = (4 * (enc->coord32.lon - b->minx) + xFracture) / dividerx4; // div with quarters + const int extrax4 = (enc->coord32.lon - b->minx) * 4 - (dx * dividerx4); // mod with quarters + + const int dividery = 90; + int dy = (b->maxy - enc->coord32.lat) / dividery; + int extray = (b->maxy - enc->coord32.lat) % dividery; + + if (extray == 0 && enc->fraclat > 0) { + dy--; + extray += dividery; + } + + if (isSpecialShape22(m)) { + SIDE = 1 + ((b->maxy - b->miny) / 90); // new side, based purely on y-distance + xSIDE = (orgSIDE * orgSIDE) / SIDE; + v += encodeSixWide(dx, SIDE - 1 - dy, xSIDE, SIDE); + } + else { + v += (dx * SIDE + dy); + } + + encodeBase31(result, v, codexlen + 1); // nameless + { + int dotp = codexlen; + if (codexm == 13) { + dotp--; + } + memmove(result + dotp, result + dotp - 1, 4); + result[dotp - 1] = '.'; + } + + if (!isSpecialShape22(m)) { + if (codexm == 22 && A < 62 && orgSIDE == 961) { + const char t = result[codexlen - 2]; + result[codexlen - 2] = result[codexlen]; + result[codexlen] = t; + } + } + + encodeExtension(result, extrax4, extray, dividerx4, dividery, extraDigits, -1, enc); // nameless + + return; + + } // in range + } +} + +// encode in m (known to fit) +static void encodeAutoHeader(char *result, const encodeRec *enc, const int m, const int extraDigits) { + int i; + int STORAGE_START = 0; + int W, H, xdiv, product; + const Boundaries *b; + + // search back to first of the group + int firstindex = m; + const int codexm = coDex(m); + while (recType(firstindex - 1) > 1 && coDex(firstindex - 1) == codexm) { + firstindex--; + } + + i = firstindex; + for (; ;) { + b = boundaries(i); + // determine how many cells + H = (b->maxy - b->miny + 89) / 90; // multiple of 10m + xdiv = xDivider4(b->miny, b->maxy); + W = ((b->maxx - b->minx) * 4 + (xdiv - 1)) / xdiv; + + // round up to multiples of 176*168... + H = 176 * ((H + 176 - 1) / 176); + W = 168 * ((W + 168 - 1) / 168); + product = (W / 168) * (H / 176) * 961 * 31; + if (recType(i) == 2) { // plus pipe + const int GOODROUNDER = codexm >= 23 ? (961 * 961 * 31) : (961 * 961); + product = ((STORAGE_START + product + GOODROUNDER - 1) / GOODROUNDER) * GOODROUNDER - STORAGE_START; + } + if (i == m) { + // encode + const int dividerx = (b->maxx - b->minx + W - 1) / W; + const int vx = (enc->coord32.lon - b->minx) / dividerx; + const int extrax = (enc->coord32.lon - b->minx) % dividerx; + + const int dividery = (b->maxy - b->miny + H - 1) / H; + int vy = (b->maxy - enc->coord32.lat) / dividery; + int extray = (b->maxy - enc->coord32.lat) % dividery; + + const int codexlen = (codexm / 10) + (codexm % 10); + int value = (vx / 168) * (H / 176); + + if (extray == 0 && enc->fraclat > 0) { + vy--; + extray += dividery; + } + + value += (vy / 176); + + // PIPELETTER ENCODE + encodeBase31(result, (STORAGE_START / (961 * 31)) + value, codexlen - 2); + result[codexlen - 2] = '.'; + encode_triple(result + codexlen - 1, vx % 168, vy % 176); + + encodeExtension(result, extrax << 2, extray, dividerx << 2, dividery, extraDigits, -1, enc); // autoheader + return; + } + STORAGE_START += product; + i++; + } +} + +static void encoderEngine(const int ccode, const encodeRec *enc, const int stop_with_one_result, + const int extraDigits, const int requiredEncoder, const int ccode_override) { + int from, upto; + + if ((enc == NULL) || (ccode < 0) || (ccode > ccode_earth)) { + return; + } // bad arguments + + from = firstrec(ccode); + upto = lastrec(ccode); + + if (!fitsInsideBoundaries(&enc->coord32, boundaries(upto))) { + return; + } + + /////////////////////////////////////////////////////////// + // look for encoding options + /////////////////////////////////////////////////////////// + { + int i; + char result[128]; + int result_counter = 0; + + *result = 0; + for (i = from; i <= upto; i++) { + if (fitsInsideBoundaries(&enc->coord32, boundaries(i))) { + if (isNameless(i)) { + encodeNameless(result, enc, ccode, extraDigits, i); + } + else if (recType(i) > 1) { + encodeAutoHeader(result, enc, i, extraDigits); + } + else if ((i == upto) && isSubdivision(ccode)) { + // *** do a recursive call for the parent *** + encoderEngine(ParentTerritoryOf(ccode), enc, stop_with_one_result, extraDigits, requiredEncoder, + ccode); + return; /**/ + } + else // must be grid + { + // skip isRestricted records unless there already is a result + if (result_counter || !isRestricted(i)) { + if (coDex(i) < 54) { + char headerletter = (char) ((recType(i) == 1) ? headerLetter(i) : 0); + encodeGrid(result, enc, i, extraDigits, headerletter); + } + } + } + + // =========== handle result (if any) + if (*result) { + result_counter++; + + repack_if_alldigits(result, 0); + + if ((requiredEncoder < 0) || (requiredEncoder == i)) { + const int cc = (ccode_override >= 0 ? ccode_override : ccode); + if (*result && enc->mapcodes && (enc->mapcodes->count < MAX_NR_OF_MAPCODE_RESULTS)) { + char *s = enc->mapcodes->mapcode[enc->mapcodes->count++]; + if (cc == ccode_earth) { + strcpy(s, result); + } else { + getTerritoryIsoName(s, cc + 1, 0); + strcat(s, " "); + strcat(s, result); + } + } + if (requiredEncoder == i) { return; } + } + if (stop_with_one_result) { return; } + *result = 0; // clear for next iteration + } + } + } // for i + } +} + +// pass point to an array of pointers (at least 42), will be made to point to result strings... +// returns nr of results; +static int encodeLatLonToMapcodes_internal(char **v, Mapcodes *mapcodes, + const double lat, const double lon, + const int tc, const int stop_with_one_result, + const int requiredEncoder, const int extraDigits) { + encodeRec enc; + enc.mapcodes = mapcodes; + enc.mapcodes->count = 0; + + convertCoordsToMicrosAndFractions(&enc.coord32, &enc.fraclat, &enc.fraclon, lat, lon); + + if (tc <= 0) // ALL results? + { +#ifdef FAST_ENCODE + const int sum = enc.coord32.lon + enc.coord32.lat; + int coord = enc.coord32.lon; + int i = 0; // pointer into redivar + for (; ;) { + const int r = redivar[i++]; + if (r >= 0 && r < 1024) { // leaf? + int j; + for (j = 0; j <= r; j++) { + const int ctry = (j == r ? ccode_earth : redivar[i + j]); + encoderEngine(ctry, &enc, stop_with_one_result, extraDigits, requiredEncoder, -1); + if ((stop_with_one_result || (requiredEncoder >= 0)) && (enc.mapcodes->count > 0)) { break; } + } + break; + } + else { + coord = sum - coord; + if (coord > r) { + i = redivar[i]; + } + else { + i++; + } + } } - if (s == NULL) { - return -23; +#else + int i; + for(i=0;i= 0)) && (enc.mapcodes->count > 0)) { break; } + } +#endif + } + else { + encoderEngine((tc - 1), &enc, stop_with_one_result, extraDigits, requiredEncoder, -1); + } + + if (v) { + int i; + for (i = 0; i < enc.mapcodes->count; i++) { + char *s = &enc.mapcodes->mapcode[i][0]; + char *p = strchr(s, ' '); + if (p == NULL) { + v[i * 2 + 1] = (char *) "AAA"; + v[i * 2] = s; + } + else { + *p++ = 0; + v[i * 2 + 1] = s; + v[i * 2] = p; + } } } - // return result - return (int) ((s - entity_iso) / 4); + + return enc.mapcodes->count; } -#endif -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// // -// HIGH-PRECISION EXTENSION (0-8 characters) +// DECODING // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static void encodeExtension(char *result, const int extrax4, const int extray, const int dividerx4, - const int dividery, int extraDigits, const int ydirection, - const encodeRec *enc) // append extra characters to result for more precision -{ - if (extraDigits > 0) { // anything to do? - char *s = result + strlen(result); - double factorx = (double) MAX_PRECISION_FACTOR * dividerx4; // perfect integer! - double factory = (double) MAX_PRECISION_FACTOR * dividery; // perfect integer! - double valx = ((double) MAX_PRECISION_FACTOR * extrax4) + enc->fraclon; // perfect integer! - double valy = ((double) MAX_PRECISION_FACTOR * extray ) + (ydirection * enc->fraclat); // perfect integer! - - // 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 > MAX_PRECISION_DIGITS) { - extraDigits = MAX_PRECISION_DIGITS; - } - - *s++ = '-'; - - for(;;) { - int gx, gy; - - factorx /= 30; - gx = (int)(valx / factorx); - - factory /= 30; - gy = (int)(valy / factory); - - *s++ = encode_chars[(gy / 5) * 5 + (gx / 6)]; - if (--extraDigits == 0) { break; } - - *s++ = encode_chars[(gy % 5) * 6 + (gx % 6)]; - if (--extraDigits == 0) { break; } - - valx -= factorx * gx; // for next iteration - valy -= factory * gy; // for next iteration - } - *s = 0; // terminate the result - } -} +/////////////////////////////////////////////////////////////////////////////////////////////// -#define decodeChar(c) decode_chars[(unsigned char)c] // force c to be in range of the index, between 0 and 255 +typedef struct { + // input + const char *orginput; // original full input string + char minput[MAX_MAPCODE_RESULT_LEN]; // room to manipulate clean copy of input + const char *mapcode; // input mapcode (first character of proper mapcode excluding territory code) + const char *extension; // input extension (or empty) + int context; // input territory context (or negative) + const char *iso; // input territory alphacode (context) + // output + point result; // result + point32 coord32; // result in integer arithmetic (microdegrees) + MapcodeZone zone; // result zone (in "DegreeFractions") +} decodeRec; +// decode the high-precision extension (0-8 characters) // this routine takes the integer-arithmeteic decoding results (dec->coord32), adds precision, // and determines result zone (dec->zone); returns negative in case of error. -static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int lon_offset4, int extremeLat32, int maxLon32) { - double lat1,lon4; +static int decodeExtension(decodeRec *dec, + int dividerx4, int dividery, + const int lon_offset4, + const int extremeLat32, const int maxLon32) { + double lat1, lon4; const char *extrapostfix = dec->extension; int lon32 = 0; int lat32 = 0; int processor = 1; int odd = 0; - if (strlen(extrapostfix)>MAX_PRECISION_DIGITS) { + if (strlen(extrapostfix) > MAX_PRECISION_DIGITS) { return -79; // too many digits } while (*extrapostfix) { @@ -463,7 +1063,7 @@ static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int lon_ column2 = 0; } - processor *= 30; + processor *= 30; lon32 = lon32 * 30 + column1 * 6 + column2; lat32 = lat32 * 30 + row1 * 5 + row2; } @@ -474,21 +1074,22 @@ static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int lon_ processor *= 30; } - lon4 = (dec->coord32.lon * 4 * (double)MAX_PRECISION_FACTOR) + ((lon32 * (double)dividerx4) ) + (lon_offset4 * (double)MAX_PRECISION_FACTOR); - lat1 = (dec->coord32.lat * (double)MAX_PRECISION_FACTOR) + ((lat32 * (double)dividery ) ); + lon4 = (dec->coord32.lon * 4 * (double) MAX_PRECISION_FACTOR) + ((lon32 * (double) dividerx4)) + + (lon_offset4 * (double) MAX_PRECISION_FACTOR); + lat1 = (dec->coord32.lat * (double) MAX_PRECISION_FACTOR) + ((lat32 * (double) dividery)); - // determine the range of coordinates that are encode to this mapcode + // determine the range of coordinates that are encoded to this mapcode if (odd) { - setFromFractions(&dec->zone, lat1, lon4, 5 * dividery , 6 * dividerx4); + setFromFractions(&dec->zone, lat1, lon4, 5 * dividery, 6 * dividerx4); } else { - setFromFractions(&dec->zone, lat1, lon4, dividery , dividerx4); + setFromFractions(&dec->zone, lat1, lon4, dividery, dividerx4); } // restrict the coordinate range to the extremes that were provided if (dec->zone.fmaxx > maxLon32 * MICROLON_TO_FRACTIONS_FACTOR) { dec->zone.fmaxx = maxLon32 * MICROLON_TO_FRACTIONS_FACTOR; } - if (dividery >= 0 ) { + if (dividery >= 0) { if (dec->zone.fmaxy > extremeLat32 * MICROLAT_TO_FRACTIONS_FACTOR) { dec->zone.fmaxy = extremeLat32 * MICROLAT_TO_FRACTIONS_FACTOR; } @@ -501,23 +1102,6 @@ static int decodeExtension(decodeRec *dec, int dividerx4, int dividery, int lon_ return isEmpty(&dec->zone) ? -45 : 0; } - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// LOWEST-LEVEL BASE31 ENCODING/DECODING -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// encode 'value' into result[nrchars] -static void encodeBase31(char *result, int value, int nrchars) { - result[nrchars] = 0; // zero-terminate! - while (nrchars-- > 0) { - result[nrchars] = encode_chars[value % 31]; - value /= 31; - } -} - // decode 'code' until either a dot or an end-of-string is encountered static int decodeBase31(const char *code) { int value = 0; @@ -527,27 +1111,6 @@ static int decodeBase31(const char *code) { return value; } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// SECOND-LEVEL ECCODING/DECODING : RELATIVE -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static void encode_triple(char *result, int difx, int dify) { - if (dify < 4 * 34) // first 4(x34) rows of 6(x28) wide - { - *result = encode_chars[ ((difx / 28) + 6 * (dify / 34)) ]; - encodeBase31(result + 1, ((difx % 28) * 34 + (dify % 34)), 2); - } - else // bottom row - { - *result = encode_chars[ (difx / 24) + 24 ]; - encodeBase31(result + 1, (difx % 24) * 40 + (dify - 136), 2); - } -} // encode_triple - - static void decode_triple(const char *result, int *difx, int *dify) { // decode the first character const int c1 = decodeChar(*result++); @@ -564,32 +1127,8 @@ static void decode_triple(const char *result, int *difx, int *dify) { } } // decode_triple - - - - - - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// -// SECOND-LEVEL ECCODING/DECODING : GRID -// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static int encodeSixWide(int x, int y, int width, int height) { - int v; - int D = 6; - int col = x / 6; - const int maxcol = (width - 4) / 6; - if (col >= maxcol) { - col = maxcol; - D = width - maxcol * 6; - } - v = (height * 6 * col) + (height - 1 - y) * D + (x - col * 6); - return v; -} - -static void decodeSixWide(int v, int width, int height, int *x, int *y) { +static void decodeSixWide(const int v, const int width, const int height, + int *x, int *y) { int w; int D = 6; int col = v / (height * 6); @@ -604,8 +1143,10 @@ static void decodeSixWide(int v, int width, int height, int *x, int *y) { *y = height - 1 - (w / D); } +// *** mid-level encode routines *** + // decodes dec->mapcode in context of territory rectangle m; returns negative if error -static int decodeGrid(decodeRec *dec, int m, int hasHeaderLetter) { +static int decodeGrid(decodeRec *dec, const int m, const int hasHeaderLetter) { const char *input = (hasHeaderLetter ? dec->mapcode + 1 : dec->mapcode); const int codexlen = (int) (strlen(input) - 1); int prelen = (int) (strchr(input, '.') - input); @@ -660,211 +1201,66 @@ static int decodeGrid(decodeRec *dec, int m, int hasHeaderLetter) { { - const mminforec *b = boundaries(m); + const Boundaries *b = boundaries(m); const int ygridsize = (b->maxy - b->miny + divy - 1) / divy; // microdegrees per cell const int xgridsize = (b->maxx - b->minx + divx - 1) / divx; // microdegrees per cell - // encode relative to THE CORNER of this cell - rely = b->miny + (rely * ygridsize); - relx = b->minx + (relx * xgridsize); - - { - const int xp = xside[postlen]; - const int dividerx = ((xgridsize + xp - 1) / xp); - const int yp = yside[postlen]; - const int dividery = ((ygridsize + yp - 1) / yp); - // decoderelative - - { - char *r = result + prelen + 1; - int difx, dify; - - if (postlen == 3) // decode special - { - decode_triple(r, &difx, &dify); - } - else { - int v; - if (postlen == 4) { - char t = r[1]; - r[1] = r[2]; - r[2] = t; - } // swap - v = decodeBase31(r); - difx = (v / yp); - dify = (v % yp); - if (postlen == 4) { - char t = r[1]; - r[1] = r[2]; - r[2] = t; - } // swap back - } - - // reverse y-direction - dify = yp - 1 - dify; - - dec->coord32.lon = relx + (difx * dividerx); - dec->coord32.lat = rely + (dify * dividery); - if (!fitsInside(&dec->coord32,m)) { - return -912; - } - - { - const int decodeMaxx = ((relx + xgridsize) < b->maxx) ? (relx + xgridsize) : b->maxx; - const int decodeMaxy = ((rely + ygridsize) < b->maxy) ? (rely + ygridsize) : b->maxy; - return decodeExtension(dec, dividerx << 2, dividery, 0, decodeMaxy, decodeMaxx); // grid - } - } // decoderelative - } - } - } - } -} - - -// returns *result==0 in case of error -static void encodeGrid(char *result, const encodeRec *enc, int const m, int extraDigits, char headerLetter) { - const mminforec *b = boundaries(m); - - const int orgcodex = coDex(m); - int codexm = orgcodex; - if (codexm == 21) { - codexm = 22; - } - else if (codexm == 14) { - codexm = 23; - } - - *result = 0; - if (headerLetter) { result++; } - - { // encode - int divx, divy; - const int prelen = codexm / 10; - const int postlen = codexm % 10; - - divy = smartDiv(m); - if (divy == 1) { - divx = xside[prelen]; - divy = yside[prelen]; - } - else { - divx = (nc[prelen] / divy); - } - - { // grid - const int ygridsize = (b->maxy - b->miny + divy - 1) / divy; - const int xgridsize = (b->maxx - b->minx + divx - 1) / divx; - int rely = enc->coord32.lat - b->miny; - int x = enc->coord32.lon; - int relx = x - b->minx; - - if (relx < 0) { - relx += 360000000; - x += 360000000; - } - else if (relx >= 360000000) // 1.32 fix FIJI edge case - { - relx -= 360000000; - x -= 360000000; - } - - rely /= ygridsize; - relx /= xgridsize; - - if (relx >= divx || rely >= divy) { - return; - } - - { // prefix - int v; - if (divx != divy && prelen > 2) { - v = encodeSixWide(relx, rely, divx, divy); - } else { - v = relx * divy + (divy - 1 - rely); - } - encodeBase31(result, v, prelen); - } // prefix - - if (prelen == 4 && divx == 961 && divy == 961) { - const char t = result[1]; - result[1] = result[2]; - result[2] = t; - } - - rely = b->miny + (rely * ygridsize); - relx = b->minx + (relx * xgridsize); - - { // postfix - const int dividery = ((ygridsize + yside[postlen] - 1) / yside[postlen]); - const int dividerx = ((xgridsize + xside[postlen] - 1) / xside[postlen]); - int extrax, extray; - - { - char *resultptr = result + prelen; - - - int difx = x - relx; - int dify = enc->coord32.lat - rely; - - *resultptr++ = '.'; - - extrax = difx % dividerx; - extray = dify % dividery; - difx /= dividerx; - dify /= dividery; - - - // reverse y-direction - dify = yside[postlen] - 1 - dify; - - if (postlen == 3) // encode special - { - encode_triple(resultptr, difx, dify); - } - else { - encodeBase31(resultptr, (difx) * yside[postlen] + dify, postlen); - // swap 4-int codes for readability - if (postlen == 4) { - char t = resultptr[1]; - resultptr[1] = resultptr[2]; - resultptr[2] = t; - } - } - } + // encode relative to THE CORNER of this cell + rely = b->miny + (rely * ygridsize); + relx = b->minx + (relx * xgridsize); - if (orgcodex == 14) { - result[2] = result[1]; - result[1] = '.'; - } + { + const int xp = xside[postlen]; + const int dividerx = ((xgridsize + xp - 1) / xp); + const int yp = yside[postlen]; + const int dividery = ((ygridsize + yp - 1) / yp); + // decoderelative - encodeExtension(result, extrax << 2, extray, dividerx << 2, dividery, extraDigits, 1, enc); // grid - if (headerLetter) { - result--; - *result = headerLetter; - } - } // postfix - } // grid - } // encode -} + { + char *r = result + prelen + 1; + int difx, dify; + if (postlen == 3) // decode special + { + decode_triple(r, &difx, &dify); + } + else { + int v; + if (postlen == 4) { + char t = r[1]; + r[1] = r[2]; + r[2] = t; + } // swap + v = decodeBase31(r); + difx = (v / yp); + dify = (v % yp); + if (postlen == 4) { + char t = r[1]; + r[1] = r[2]; + r[2] = t; + } // swap back + } -// find first territory rectangle of the same type as m -static int firstNamelessRecord(int m, int firstcode) { - int i = m; - const int codexm = coDex(m); - while (i >= firstcode && coDex(i) == codexm && isNameless(i)) { i--; } - return (i + 1); -} + // reverse y-direction + dify = yp - 1 - dify; -// count all territory rectangles of the same type as m -static int countNamelessRecords(int m, int firstcode) { - const int i = firstNamelessRecord(m, firstcode); - const int codexm = coDex(m); - while (coDex(m) == codexm) { m++; } - return (m - i); -} + dec->coord32.lon = relx + (difx * dividerx); + dec->coord32.lat = rely + (dify * dividery); + if (!fitsInsideBoundaries(&dec->coord32, boundaries(m))) { + return -912; + } + { + const int decodeMaxx = ((relx + xgridsize) < b->maxx) ? (relx + xgridsize) : b->maxx; + const int decodeMaxy = ((rely + ygridsize) < b->maxy) ? (rely + ygridsize) : b->maxy; + return decodeExtension(dec, dividerx << 2, dividery, 0, decodeMaxy, decodeMaxx); // grid + } + } // decoderelative + } + } + } + } +} // decodes dec->mapcode in context of territory rectangle m, territory dec->context // Returns negative in case of error @@ -895,7 +1291,7 @@ static int decodeNameless(decodeRec *dec, int m) { int swapletters = 0; int xSIDE; int X; - const mminforec *b; + const Boundaries *b; // make copy of input, so we can swap around letters during the decoding char result[32]; @@ -962,250 +1358,46 @@ static int decodeNameless(decodeRec *dec, int m) { m = (F + X); - xSIDE = SIDE = smartDiv(m); - - b = boundaries(m); - - // decode - { - int dx, dy; - - if (isSpecialShape22(m)) { - xSIDE *= SIDE; - SIDE = 1 + ((b->maxy - b->miny) / 90); // side purely on y range - xSIDE = xSIDE / SIDE; - - decodeSixWide(v, xSIDE, SIDE, &dx, &dy); - dy = SIDE - 1 - dy; - } - else { - dy = v % SIDE; - dx = v / SIDE; - } - - - if (dx >= xSIDE) { - return -123; - } - - { - const int dividerx4 = xDivider4(b->miny, b->maxy); // *** note: dividerx4 is 4 times too large! - const int dividery = 90; - - // *** note: FIRST multiply, then divide... more precise, larger rects - dec->coord32.lon = b->minx + ((dx * dividerx4) / 4); - dec->coord32.lat = b->maxy - (dy * dividery); - - return decodeExtension(dec, dividerx4, -dividery, ((dx * dividerx4) % 4) , b->miny,b->maxx); // nameless - } - } - } -} - - -static void repack_if_alldigits(char *input, const int aonly) { - char *s = input; - int alldigits = 1; // assume all digits - char *e; - char *dotpos = NULL; - - for (e = s; *e != 0 && *e != '-'; e++) { - if (*e < '0' || *e > '9') { - if (*e == '.' && !dotpos) { - dotpos = e; - } else { - alldigits = 0; - break; - } - } - } - e--; - s = e - 1; - if (alldigits && dotpos && - s > dotpos) // e is last char, s is one before, both are beyond dot, all characters are digits - { - if (aonly) // v1.50 - encode only using the letter A - { - const int v = ((*input) - '0') * 100 + ((*s) - '0') * 10 + ((*e) - '0'); - *input = 'A'; - *s = encode_chars[v / 32]; - *e = encode_chars[v % 32]; - } - else // encode using A,E,U - { - const int v = ((*s) - '0') * 10 + ((*e) - '0'); - *s = encode_chars[(v / 34) + 31]; - *e = encode_chars[v % 34]; - } - } -} - -// returns 1 if unpacked, 0 if left unchanged, negative if unchanged and an error was detected -static int unpack_if_alldigits(char *input) -{ // rewrite all-digit codes - char *s = input; - char *dotpos = NULL; - const int aonly = (*s == 'A' || *s == 'a'); - if (aonly) { s++; } //*** v1.50 - for (; *s != 0 && s[2] != 0 && s[2] != '-'; s++) { - if (*s == '-') { - break; - } else if (*s == '.' && !dotpos) { - dotpos = s; - } else if (decodeChar(*s) < 0 || decodeChar(*s) > 9) { - return 0; - } // nondigit, so stop - } - - if (dotpos) { - if (aonly) // v1.50 encoded only with A's - { - const int v = (s[0] == 'A' || s[0] == 'a' ? 31 : decodeChar(s[0])) * 32 + - (s[1] == 'A' || s[1] == 'a' ? 31 : decodeChar(s[1])); - *input = (char) ('0' + (v / 100)); - s[0] = (char) ('0' + ((v / 10) % 10)); - s[1] = (char) ('0' + (v % 10)); - return 1; - } // v1.50 - - if (*s == 'a' || *s == 'e' || *s == 'u' || *s == 'A' || *s == 'E' || - *s == 'U') // thus, all digits, s[2]=0, after dot - { - char *e = s + 1; // s is vowel, e is lastchar - - int v = 0; - if (*s == 'e' || *s == 'E') { - v = 34; - } else if (*s == 'u' || *s == 'U') { v = 68; } - - if (*e == 'a' || *e == 'A') { - v += 31; - } else if (*e == 'e' || *e == 'E') { - v += 32; - } else if (*e == 'u' || *e == 'U') { - v += 33; - } else if (decodeChar(*e) < 0) { - return -9; // invalid last character! - } else { v += decodeChar(*e); } - - if (v < 100) { - *s = encode_chars[(unsigned int) v / 10]; - *e = encode_chars[(unsigned int) v % 10]; - } else { - return -31; // overflow (ending in UE or UU) - } - return 1; - } - } - return 0; // no vowel just before end -} - - -// *result==0 in case of error -static void encodeNameless(char *result, const encodeRec *enc, int input_ctry, int extraDigits, int m) { - // determine how many nameless records there are (A), and which one is this (X)... - const int A = countNamelessRecords(m, firstrec(input_ctry)); - const int X = m - firstNamelessRecord(m, firstrec(input_ctry)); - - *result = 0; - - { - const int p = 31 / A; - const int r = 31 % A; // the first r items are p+1 - const int codexm = coDex(m); - const int codexlen = (codexm / 10) + (codexm % 10); - // determine side of square around centre - int SIDE; - - int storage_offset; - const mminforec *b; - - int xSIDE, orgSIDE; - - if (codexm != 21 && A <= 31) { - storage_offset = (X * p + (X < r ? X : r)) * (961 * 961); - } - else if (codexm != 21 && A < 62) { - if (X < (62 - A)) { - storage_offset = X * (961 * 961); - } - else { - storage_offset = (62 - A + ((X - 62 + A) / 2)) * (961 * 961); - if ((X + A) & 1) { - storage_offset += (16 * 961 * 31); - } - } - } - else { - const int BASEPOWER = (codexm == 21) ? 961 * 961 : 961 * 961 * 31; - int BASEPOWERA = (BASEPOWER / A); - if (A == 62) { - BASEPOWERA++; - } else { - BASEPOWERA = (961) * (BASEPOWERA / 961); - } - - storage_offset = X * BASEPOWERA; - } - - SIDE = smartDiv(m); - - b = boundaries(m); - orgSIDE = xSIDE = SIDE; - - { - int v = storage_offset; - - const int dividerx4 = xDivider4(b->miny, b->maxy); // *** note: dividerx4 is 4 times too large! - const int xFracture = (enc->fraclon / MAX_PRECISION_FACTOR); - const int dx = (4 * (enc->coord32.lon - b->minx) + xFracture) / dividerx4; // div with quarters - const int extrax4 = (enc->coord32.lon - b->minx) * 4 - (dx * dividerx4); // mod with quarters - - const int dividery = 90; - int dy = (b->maxy - enc->coord32.lat) / dividery; - int extray = (b->maxy - enc->coord32.lat) % dividery; + xSIDE = SIDE = smartDiv(m); - if (extray == 0 && enc->fraclat > 0) { - dy--; - extray += dividery; - } + b = boundaries(m); + + // decode + { + int dx, dy; if (isSpecialShape22(m)) { - SIDE = 1 + ((b->maxy - b->miny) / 90); // new side, based purely on y-distance - xSIDE = (orgSIDE * orgSIDE) / SIDE; - v += encodeSixWide(dx, SIDE - 1 - dy, xSIDE, SIDE); + xSIDE *= SIDE; + SIDE = 1 + ((b->maxy - b->miny) / 90); // side purely on y range + xSIDE = xSIDE / SIDE; + + decodeSixWide(v, xSIDE, SIDE, &dx, &dy); + dy = SIDE - 1 - dy; } else { - v += (dx * SIDE + dy); + dy = v % SIDE; + dx = v / SIDE; } - encodeBase31(result, v, codexlen + 1); // nameless - { - int dotp = codexlen; - if (codexm == 13) { - dotp--; - } - memmove(result + dotp, result + dotp - 1, 4); - result[dotp - 1] = '.'; - } - if (!isSpecialShape22(m)) { - if (codexm == 22 && A < 62 && orgSIDE == 961) { - const char t = result[codexlen - 2]; - result[codexlen - 2] = result[codexlen]; - result[codexlen] = t; - } + if (dx >= xSIDE) { + return -123; } - encodeExtension(result, extrax4, extray, dividerx4, dividery, extraDigits, -1, enc); // nameless + { + const int dividerx4 = xDivider4(b->miny, b->maxy); // *** note: dividerx4 is 4 times too large! + const int dividery = 90; - return; + // *** note: FIRST multiply, then divide... more precise, larger rects + dec->coord32.lon = b->minx + ((dx * dividerx4) / 4); + dec->coord32.lat = b->maxy - (dy * dividery); - } // in range + return decodeExtension(dec, dividerx4, -dividery, ((dx * dividerx4) % 4), b->miny, b->maxx); // nameless + } + } } } - // decodes dec->mapcode in context of territory rectangle m or one of its mates static int decodeAutoHeader(decodeRec *dec, int m) { const char *input = dec->mapcode; @@ -1223,7 +1415,7 @@ static int decodeAutoHeader(decodeRec *dec, int m) { value *= (961 * 31); for (; coDex(m) == codexm && recType(m) > 1; m++) { - const mminforec *b = boundaries(m); + const Boundaries *b = boundaries(m); // determine how many cells int H = (b->maxy - b->miny + 89) / 90; // multiple of 10m const int xdiv = xDivider4(b->miny, b->maxy); @@ -1257,195 +1449,21 @@ static int decodeAutoHeader(decodeRec *dec, int m) { dec->coord32.lat = b->maxy - vy * dividery; dec->coord32.lon = b->minx + vx * dividerx; - if (dec->coord32.lon < b->minx || dec->coord32.lon >= b->maxx || dec->coord32.lat < b->miny || - dec->coord32.lat > b->maxy) // *** CAREFUL! do this test BEFORE adding remainder... + if ((dec->coord32.lon < b->minx) || (dec->coord32.lon >= b->maxx) || + (dec->coord32.lat < b->miny) || (dec->coord32.lat > b->maxy)) // *** CAREFUL! do this test BEFORE adding remainder... { return -122; // invalid code - } + } } } - return decodeExtension(dec, dividerx << 2, -dividery, 0, b->miny,b->maxx); // autoheader decode + return decodeExtension(dec, dividerx << 2, -dividery, 0, b->miny, b->maxx); // autoheader decode } STORAGE_START += product; } // for j return -1; } -// encode in m (known to fit) -static void encodeAutoHeader(char *result, const encodeRec *enc, const int m, const int extraDigits) { - int i; - int STORAGE_START = 0; - int W, H, xdiv, product; - const mminforec *b; - - // search back to first of the group - int firstindex = m; - const int codexm = coDex(m); - while (recType(firstindex - 1) > 1 && coDex(firstindex - 1) == codexm) { - firstindex--; - } - - i = firstindex; - for(;;) { - b = boundaries(i); - // determine how many cells - H = (b->maxy - b->miny + 89) / 90; // multiple of 10m - xdiv = xDivider4(b->miny, b->maxy); - W = ((b->maxx - b->minx) * 4 + (xdiv - 1)) / xdiv; - - // round up to multiples of 176*168... - H = 176 * ((H + 176 - 1) / 176); - W = 168 * ((W + 168 - 1) / 168); - product = (W / 168) * (H / 176) * 961 * 31; - if (recType(i) == 2) { // plus pipe - const int GOODROUNDER = codexm >= 23 ? (961 * 961 * 31) : (961 * 961); - product = ((STORAGE_START + product + GOODROUNDER - 1) / GOODROUNDER) * GOODROUNDER - STORAGE_START; - } - if (i == m) { - // encode - const int dividerx = (b->maxx - b->minx + W - 1) / W; - const int vx = (enc->coord32.lon - b->minx) / dividerx; - const int extrax = (enc->coord32.lon - b->minx) % dividerx; - - const int dividery = (b->maxy - b->miny + H - 1) / H; - int vy = (b->maxy - enc->coord32.lat) / dividery; - int extray = (b->maxy - enc->coord32.lat) % dividery; - - const int codexlen = (codexm / 10) + (codexm % 10); - int value = (vx / 168) * (H / 176); - - if (extray == 0 && enc->fraclat > 0) { - vy--; - extray += dividery; - } - - value += (vy / 176); - - // PIPELETTER ENCODE - encodeBase31(result, (STORAGE_START / (961 * 31)) + value, codexlen - 2); - result[codexlen - 2] = '.'; - encode_triple(result + codexlen - 1, vx % 168, vy % 176); - - encodeExtension(result, extrax << 2, extray, dividerx << 2, dividery, extraDigits, -1, enc); // autoheader - return; - } - STORAGE_START += product; - i++; - } -} - -static void encoderEngine(const int ccode, const encodeRec *enc, const int stop_with_one_result, - const int extraDigits, const int requiredEncoder, const int ccode_override) { - int from, upto; - - if (enc == NULL || ccode < 0 || ccode > ccode_earth) { - return; - } // bad arguments - - from = firstrec(ccode); - upto = lastrec(ccode); - - if (!fitsInside(&enc->coord32, upto)) { - return; - } - - /////////////////////////////////////////////////////////// - // look for encoding options - /////////////////////////////////////////////////////////// - { - int i; - char result[128]; - int result_counter = 0; - - *result = 0; - for (i = from; i <= upto; i++) { - if (fitsInside(&enc->coord32, i)) { - if (isNameless(i)) { - encodeNameless(result, enc, ccode, extraDigits, i); - } - else if (recType(i) > 1) { - encodeAutoHeader(result, enc, i, extraDigits); - } - else if ((i == upto) && isSubdivision(ccode)) { - // *** do a recursive call for the parent *** - encoderEngine(ParentTerritoryOf(ccode), enc, stop_with_one_result, extraDigits, requiredEncoder, ccode); - return; /**/ - } - else // must be grid - { - // skip isRestricted records unless there already is a result - if (result_counter || !isRestricted(i)) { - if (coDex(i) < 54) { - char headerletter = (char) ((recType(i) == 1) ? headerLetter(i) : 0); - encodeGrid(result, enc, i, extraDigits, headerletter); - } - } - } - - // =========== handle result (if any) - if (*result) { - result_counter++; - - repack_if_alldigits(result, 0); - - if (requiredEncoder < 0 || requiredEncoder == i) { - const int cc = (ccode_override >= 0 ? ccode_override : ccode); - if (*result && enc->mapcodes && (enc->mapcodes->count < MAX_NR_OF_MAPCODE_RESULTS)) { - char *s = enc->mapcodes->mapcode[enc->mapcodes->count++]; - if (cc == ccode_earth) { - strcpy(s, result); - } else { - getTerritoryIsoName(s, cc + 1, 0); - strcat(s, " "); - strcat(s, result); - } - } - if (requiredEncoder == i) { return; } - } - if (stop_with_one_result) { return; } - *result = 0; // clear for next iteration - } - } - } // for i - } -} - -// determine the non-empty intersection zone z between a given zone and the boundaries of territory rectangle m. -// returns nonzero in case such a zone exists -int restrictZoneTo(MapcodeZone *z,const MapcodeZone *zone, const mminforec *b) { - z->fminy = zone->fminy; - z->fmaxy = zone->fmaxy; - if (z->fminy < b->miny * MICROLAT_TO_FRACTIONS_FACTOR) { - z->fminy = b->miny * MICROLAT_TO_FRACTIONS_FACTOR; - } - if (z->fmaxy > b->maxy * MICROLAT_TO_FRACTIONS_FACTOR) { - z->fmaxy = b->maxy * MICROLAT_TO_FRACTIONS_FACTOR; - } - if (z->fminy < z->fmaxy) { - double bminx = b->minx * MICROLON_TO_FRACTIONS_FACTOR; - double bmaxx = b->maxx * MICROLON_TO_FRACTIONS_FACTOR; - z->fminx = zone->fminx; - z->fmaxx = zone->fmaxx; - if (bmaxx < 0 && z->fminx > 0) { - bminx += (360000000 * MICROLON_TO_FRACTIONS_FACTOR); - bmaxx += (360000000 * MICROLON_TO_FRACTIONS_FACTOR); - } - else if (bminx > 0 && z->fmaxx < 0) { - bminx -= (360000000 * MICROLON_TO_FRACTIONS_FACTOR); - bmaxx -= (360000000 * MICROLON_TO_FRACTIONS_FACTOR); - } - if (z->fminx < bminx) { - z->fminx = bminx; - } - if (z->fmaxx > bmaxx) { - z->fmaxx = bmaxx; - } - return (z->fminx < z->fmaxx); - } - return 0; -} - // returns nonzero if error static int decoderEngine(decodeRec *dec) { @@ -1476,7 +1494,7 @@ static int decoderEngine(decodeRec *dec) { if (s) { *s++ = 0; while (*s > 0 && *s <= 32) { s++; } - ccode = convertTerritoryIsoNameToCode(w, dec->context-1) - 1; + ccode = convertTerritoryIsoNameToCode(w, dec->context - 1) - 1; } else { ccode = dec->context - 1; @@ -1485,8 +1503,8 @@ static int decoderEngine(decodeRec *dec) { if (ccode == ccode_mex && len < 8) { ccode = convertTerritoryIsoNameToCode("5MX", -1) - 1; } // special case for mexico country vs state - if (*s=='u' || *s=='U') { - strcpy(s,s+1); + if ((*s == 'u') || (*s == 'U')) { + strcpy(s, s + 1); repack_if_alldigits(s, 1); } dec->context = ccode; @@ -1504,7 +1522,7 @@ static int decoderEngine(decodeRec *dec) { *w = '0'; } else if (*w == 'I') { *w = '1'; - } else if (*w == 'A' || *w == 'E' || *w == 'U') { + } else if ((*w == 'A') || (*w == 'E') || (*w == 'U')) { hasvowels = 1; } else if (dec->extension == NULL) { hasletters = 1; @@ -1519,7 +1537,7 @@ static int decoderEngine(decodeRec *dec) { else if (*w == '-') { if (dec->extension != NULL) { return -17; // already had a hyphen - } + } dec->extension = w + 1; *w = 0; } @@ -1530,7 +1548,7 @@ static int decoderEngine(decodeRec *dec) { if (!dot) { return -27; } - if (dec->extension == NULL) { + if (dec->extension == NULL) { dec->extension = ""; } @@ -1552,7 +1570,7 @@ static int decoderEngine(decodeRec *dec) { else if (isSubdivision(ccode)) { // int mapcodes must be interpreted in the parent of a subdivision int parent = ParentTerritoryOf(ccode); - if (codex == 44 || ((codex == 34 || codex == 43) && (parent == ccode_ind || parent == ccode_mex))) { + if ((codex == 44) || ((codex == 34 || codex == 43) && (parent == ccode_ind || parent == ccode_mex))) { ccode = parent; } } @@ -1570,15 +1588,15 @@ static int decoderEngine(decodeRec *dec) { const int r = recType(i); if (r == 0) { if (isNameless(i)) { - if (((codexi == 21) && (codex == 22)) - || ((codexi == 22) && (codex == 32)) - || ((codexi == 13) && (codex == 23))) { + if (((codexi == 21) && (codex == 22)) || + ((codexi == 22) && (codex == 32)) || + ((codexi == 13) && (codex == 23))) { err = decodeNameless(dec, i); break; } } - else { - if (codexi == codex || ((codex == 22) && (codexi == 21))) { + else { + if ((codexi == codex) || ((codex == 22) && (codexi == 21))) { err = decodeGrid(dec, i, 0); // first of all, make sure the zone fits the country @@ -1587,13 +1605,13 @@ static int decoderEngine(decodeRec *dec) { if ((err == 0) && isRestricted(i)) { int nrZoneOverlaps = 0; int j; - + // *** make sure decode fits somewhere *** dec->result = getMidPointFractions(&dec->zone); dec->coord32 = convertFractionsToCoord32(&dec->result); for (j = i - 1; j >= from; j--) { // look in previous rects if (!isRestricted(j)) { - if (fitsInside(&dec->coord32, j)) { + if (fitsInsideBoundaries(&dec->coord32, boundaries(j))) { nrZoneOverlaps = 1; break; } @@ -1602,31 +1620,31 @@ static int decoderEngine(decodeRec *dec) { if (!nrZoneOverlaps) { MapcodeZone zfound; - mminforec prevu; + Boundaries prevu; int prevj = -1; for (j = from; j < i; j++) { // try all smaller rectangles j - if (!isRestricted(j)) { - MapcodeZone z; - if (restrictZoneTo(&z,&dec->zone,boundaries(j))) { - nrZoneOverlaps++; - if (nrZoneOverlaps == 1) { - // first fit! remember... - zoneCopyFrom(&zfound,&z); - prevj = j; - memcpy(&prevu,boundaries(j),sizeof(mminforec)); + if (!isRestricted(j)) { + MapcodeZone z; + if (restrictZoneTo(&z, &dec->zone, boundaries(j))) { + nrZoneOverlaps++; + if (nrZoneOverlaps == 1) { + // first fit! remember... + zoneCopyFrom(&zfound, &z); + prevj = j; + memcpy(&prevu, boundaries(j), sizeof(Boundaries)); + } + else { // nrZoneOverlaps >= 2 + // more than one hit + break; // give up + } } - else { // nrZoneOverlaps >= 2 - // more than one hit - break; // give up - } - } - } // isRestricted + } // isRestricted } // for j // if several sub-areas intersect, just return the whole zone // (the center of which may NOT re-encode to the same mapcode!) if (nrZoneOverlaps == 1) { // found exactly ONE intersection? - zoneCopyFrom(&dec->zone,&zfound); + zoneCopyFrom(&dec->zone, &zfound); } } @@ -1638,14 +1656,15 @@ static int decoderEngine(decodeRec *dec) { } } } - else if (r == 1) { + else if (r == 1) { if (codex == codexi + 10 && headerLetter(i) == *s) { err = decodeGrid(dec, i, 1); break; } } else { //r>1 - if (((codex == 23) && (codexi == 22)) || ((codex == 33) && (codexi == 23))) { + if (((codex == 23) && (codexi == 22)) || + ((codex == 33) && (codexi == 23))) { err = decodeAutoHeader(dec, i); break; } @@ -1668,42 +1687,19 @@ static int decoderEngine(decodeRec *dec) { dec->result = convertFractionsToDegrees(&dec->result); // normalise between =180 and 180 - if (dec->result.lat < -90.0) { dec->result.lat = -90.0; } - if (dec->result.lat > 90.0) { dec->result.lat = 90.0; } + if (dec->result.lat < -90.0) { dec->result.lat = -90.0; } + if (dec->result.lat > 90.0) { dec->result.lat = 90.0; } if (dec->result.lon < -180.0) { dec->result.lon += 360.0; } if (dec->result.lon >= 180.0) { dec->result.lon -= 360.0; } return 0; } - - -// PI -#define _PI 3.14159265358979323846 - -// Radius of Earth. -#define EARTH_RADIUS_X_METERS 6378137 -#define EARTH_RADIUS_Y_METERS 6356752 - -// Circumference of Earth. -#define EARTH_CIRCUMFERENCE_X (EARTH_RADIUS_X_METERS * 2 * _PI) -#define EARTH_CIRCUMFERENCE_Y (EARTH_RADIUS_Y_METERS * 2 * _PI) - -// Meters per degree latitude is fixed. For longitude: use factor * cos(midpoint of two degree latitudes). -#define METERS_PER_DEGREE_LAT (EARTH_CIRCUMFERENCE_Y / 360.0) -#define METERS_PER_DEGREE_LON (EARTH_CIRCUMFERENCE_X / 360.0) - -double distanceInMeters(double latDeg1, double lonDeg1, double latDeg2, double lonDeg2) { - if (lonDeg1 < 0 && lonDeg2 > 1) { lonDeg1 += 360; } - if (lonDeg2 < 0 && lonDeg1 > 1) { lonDeg2 += 360; } - { - const double dy = (latDeg2 - latDeg1) * METERS_PER_DEGREE_LAT; - const double dx = (lonDeg2 - lonDeg1) * METERS_PER_DEGREE_LON * cos((latDeg1 + latDeg2) * _PI / 360.0); - return sqrt(dx * dx + dy * dy); - } -} - - +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// Alphabet support +// +/////////////////////////////////////////////////////////////////////////////////////////////// #ifdef SUPPORT_FOREIGN_ALPHABETS @@ -1758,7 +1754,7 @@ static struct { {0x03B1, 0x03c9, "ABGDFZHQIKLMNCOJP?STYVXRW"}, // Greek lowercase {0x10d0, 0x10ef, "AB?CE?D?UF?GHOJ?KLMINPQRSTVW?XYZ"}, // Georgisch lowercase {0x0562, 0x0586, "BCDE??FGHI?J?KLM?N?U?PQ?R??STVWXYZ?OA"}, // Armenian lowercase - {0, 0, NULL} + {0, 0, NULL} }; @@ -1808,8 +1804,8 @@ static UWORD *encode_utf16(UWORD *unibuf, const int maxlen, const char *mapcode, const char *r = mapcode; while (*r != 0 && w < e) { char c = *r++; - if (c >= 'a' && c <= 'z') { c += ('A' - 'a'); } - if (c < 0 || c > 'Z') { // not in any valid range? + if ((c >= 'a') && (c <= 'z')) { c += ('A' - 'a'); } + if ((c < 0) || (c > 'Z')) { // not in any valid range? *w++ = '?'; } else if (c < 'A') { // valid but not a letter (e.g. a dot, a space...) *w++ = (UWORD) c; // leave untranslated @@ -1817,13 +1813,66 @@ static UWORD *encode_utf16(UWORD *unibuf, const int maxlen, const char *mapcode, *w++ = asc2lan[language][c - 'A']; } } - *w = 0; - return unibuf; + *w = 0; + return unibuf; +} + +// PUBLIC - convert as much as will fit of mapcode into unibuf +UWORD *convertToAlphabet(UWORD *unibuf, int maxlength, const char *mapcode, int alphabet) // 0=roman, 2=cyrillic +{ + UWORD *startbuf = unibuf; + UWORD *lastspot = &unibuf[maxlength - 1]; + if (maxlength > 0) { + char u[MAX_MAPCODE_RESULT_LEN]; + + // skip leading spaces + while (*mapcode > 0 && *mapcode <= 32) { mapcode++; } + + // straight-copy everything up to and including first space + { + const char *e = strchr(mapcode, ' '); + if (e) { + while (mapcode <= e) { + if (unibuf == lastspot) { // buffer fully filled? + // zero-terminate and return + *unibuf = 0; + return startbuf; + } + *unibuf++ = *mapcode++; + } + } + } + + // re-pack E/U-voweled mapcodes when necessary: + if (asc2lan[alphabet][4] == 0x003f) { // alphabet has no letter E + if (strchr(mapcode, 'E') || strchr(mapcode, 'U') || + strchr(mapcode, 'e') || strchr(mapcode, 'u')) { + // copy trimmed mapcode into temporary buffer u + int len = (int) strlen(mapcode); + if (len > MAX_MAPCODE_RESULT_LEN - 1) { + len = MAX_MAPCODE_RESULT_LEN - 1; + } + while (len > 0 && mapcode[len - 1] > 0 && mapcode[len - 1] <= 32) { len--; } + memcpy(u, mapcode, len); + u[len] = 0; + // re-pack into A-voweled mapcode + unpack_if_alldigits(u); + repack_if_alldigits(u, 1); + mapcode = u; + } + } + encode_utf16(unibuf, 1 + (int) (lastspot - unibuf), mapcode, alphabet); + } + return startbuf; } - #endif +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// compareWithMapcodeFormat +// +/////////////////////////////////////////////////////////////////////////////////////////////// #define TOKENSEP 0 #define TOKENDOT 1 @@ -1903,7 +1952,7 @@ int compareWithMapcodeFormat(const char *s, int fullcode) { token = TOKENHYPH; } else if (*s == 0) { token = TOKENZERO; - } else if (*s == ' ' || *s == '\t') { + } else if ((*s == ' ') || (*s == '\t')) { token = TOKENSEP; } else { const signed char c = decode_chars[(unsigned char) *s]; @@ -1935,115 +1984,18 @@ int compareWithMapcodeFormat(const char *s, int fullcode) { } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// // -// Engine +// PUBLIC INTERFACE // -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -static void convertCoordsToMicrosAndFractions(point32 *coord32, int *fraclat, int *fraclon, double lat, double lon) { - double frac; - if (lat < -90) { lat = -90; } else if (lat > 90) { lat = 90; } - lat += 90; // lat now [0..180] - lat *= (double) 810000000000; - frac = floor(lat+0.1); - coord32->lat = (int) (frac / (double) 810000); - if (fraclat) { - frac -= ((double)coord32->lat * (double) 810000); - *fraclat = (int) frac; - } - coord32->lat -= 90000000; - - lon -= (360.0 * floor(lon / 360)); // lon now in [0..360> - lon *= (double)3240000000000; - frac = floor(lon+0.1); - coord32->lon = (int) (frac / (double)3240000); - if (fraclon) { - frac -= (double)coord32->lon * (double)3240000; - *fraclon = (int) frac; - } - if (coord32->lon >= 180000000) - coord32->lon -= 360000000; -} - -// pass point to an array of pointers (at least 42), will be made to point to result strings... -// returns nr of results; -static int encodeLatLonToMapcodes_internal(char **v, Mapcodes *mapcodes, double lat, double lon, - const int tc, const int stop_with_one_result, - const int requiredEncoder, const int extraDigits) -{ - encodeRec enc; - enc.mapcodes = mapcodes; - enc.mapcodes->count = 0; - - convertCoordsToMicrosAndFractions(&enc.coord32, &enc.fraclat, &enc.fraclon, lat, lon); - - if (tc <= 0) // ALL results? - { -#ifdef FAST_ENCODE - const int sum = enc.coord32.lon + enc.coord32.lat; - int coord = enc.coord32.lon; - int i = 0; // pointer into redivar - for (; ;) { - const int r = redivar[i++]; - if (r >= 0 && r < 1024) { // leaf? - int j; - for (j = 0; j <= r; j++) { - const int ctry = (j == r ? ccode_earth : redivar[i + j]); - encoderEngine(ctry, &enc, stop_with_one_result, extraDigits, requiredEncoder, -1); - if ((stop_with_one_result || requiredEncoder >= 0) && enc.mapcodes->count > 0) { break; } - } - break; - } - else { - coord = sum - coord; - if (coord > r) { - i = redivar[i]; - } - else { - i++; - } - } - } -#else - int i; - for(i=0;i=0) && enc.mapcodes->count > 0) break; - } -#endif - } - else { - encoderEngine((tc - 1), &enc, stop_with_one_result, extraDigits, requiredEncoder, -1); - } - - if (v) { - int i; - for (i = 0; i < enc.mapcodes->count; i++) { - char *s = &enc.mapcodes->mapcode[i][0]; - char *p = strchr(s, ' '); - if (p == NULL) { - v[i * 2 + 1] = (char *) "AAA"; - v[i * 2] = s; - } - else { - *p++ = 0; - v[i * 2 + 1] = s; - v[i * 2] = p; - } - } - } - - return enc.mapcodes->count; -} - +/////////////////////////////////////////////////////////////////////////////////////////////// -// threadsafe -char *getTerritoryIsoName(char *result, int territoryCode, - int format) // formats: 0=full 1=short (returns empty string in case of error) +// PUBLIC - returns name of territoryCode in (sufficiently large!) result string. +// formats: 0=full 1=short +// returns empty string in case of error +char *getTerritoryIsoName(char *result, int territoryCode, int format) { - if (territoryCode < 1 || territoryCode > MAX_MAPCODE_TERRITORY_CODE) { + if ((territoryCode < 1) || (territoryCode > MAX_MAPCODE_TERRITORY_CODE)) { *result = 0; } else { const int p = ParentLetter(territoryCode - 1); @@ -2062,14 +2014,15 @@ char *getTerritoryIsoName(char *result, int territoryCode, return result; } -// returns negative if tc is not a code that has a parent country +// PUBLIC - returns negative if territoryCode tc is not a code that has a parent country int getParentCountryOf(int tc) { const int parentccode = ParentTerritoryOf(tc - 1); // returns parent ccode or -1 if (parentccode >= 0) { return parentccode + 1; } return -1; } -// returns tc if tc is a country, parent country if tc is a state, -1 if tc is invalid +// PUBLIC - returns tc if territoryCode tc is a country, or parent country if tc is a state. +// returns megative if tc is invalid. int getCountryOrParentCountry(int tc) { if (tc > 0 && tc < MAX_MAPCODE_TERRITORY_CODE) { const int tp = getParentCountryOf(tc); @@ -2079,15 +2032,46 @@ int getCountryOrParentCountry(int tc) { return -1; } -#ifdef FAST_ALPHA +// PUBLIC - returns nonzero if coordinate is near more than one territory border +int multipleBordersNearby(double lat, double lon, int territoryCode) { + const int ccode = territoryCode - 1; + if ((ccode >= 0) && (ccode < ccode_earth)) { // valid territory, not earth + const int parentTerritoryCode = getParentCountryOf(territoryCode); + if (parentTerritoryCode >= 0) { + // there is a parent! check its borders as well... + if (multipleBordersNearby(lat, lon, parentTerritoryCode)) { + return 1; + } + } + { + int m; + int nrFound = 0; + const int from = firstrec(ccode); + const int upto = lastrec(ccode); + point32 coord32; + convertCoordsToMicrosAndFractions(&coord32, NULL, NULL, lat, lon); + for (m = upto; m >= from; m--) { + if (!isRestricted(m)) { + if (isNearBorderOf(&coord32, boundaries(m))) { + nrFound++; + if (nrFound > 1) { + return 1; + } + } + } + } + } + } + return 0; +} -int cmp_alphacode(const void *e1, const void *e2) { +static int cmp_alphacode(const void *e1, const void *e2) { const alphaRec *a1 = (const alphaRec *) e1; const alphaRec *a2 = (const alphaRec *) e2; return strcmp(a1->alphaCode, a2->alphaCode); } // cmp -int binfindmatch(int parentcode, const char *str) { +static int binfindmatch(const int parentcode, const char *str) { // build a 4-letter uppercase search term char tmp[5]; if (parentcode < 0) { return -1; } @@ -2115,20 +2099,19 @@ int binfindmatch(int parentcode, const char *str) { return -1; } -#endif - -int convertTerritoryIsoNameToCode(const char *string, int optional_tc) // optional_tc: pass 0 or negative if unknown +// PUBLIC - returns territoryCode of string (or negative if not found). +// optional_tc: context territoryCode to handle ambiguities (pass <=0 if unknown). +int convertTerritoryIsoNameToCode(const char *string, int optional_tc) { const int ccode = optional_tc - 1; if (string == NULL) { return -1; } while (*string > 0 && *string <= 32) { string++; } // skip leading whitespace -#ifdef FAST_ALPHA if (string[0] && string[1]) { if (string[2] == '-') { - return binfindmatch(disambiguate_str(string, 2), string + 3); + return binfindmatch(getParentcode(string, 2), string + 3); } else if (string[2] && string[3] == '-') { - return binfindmatch(disambiguate_str(string, 3), string + 4); + return binfindmatch(getParentcode(string, 3), string + 4); } else if (optional_tc > 0) { int parentcode = parentnumber[ccode]; int b = binfindmatch(parentcode, string); @@ -2139,29 +2122,13 @@ int convertTerritoryIsoNameToCode(const char *string, int optional_tc) // option return binfindmatch(0, string); } // else, fail: return -1; -#else - if (ccode < 0 || strchr(string, '-') || strlen(string) > 3) { - ccode = ccode_of_iso3(string, -1); // ignore optional_tc - } - else // there is a ccode, there is no hyphen in the string, and the string is as most 3 chars - { - char tmp[12]; - const int tc = getCountryOrParentCountry(optional_tc); - - strcpy(tmp, convertTerritoryCodeToIsoName(tc, 1)); // short parent country code - strcat(tmp, "-"); - strcat(tmp, string); - ccode = ccode_of_iso3(tmp, -1); - } - if (ccode < 0) { return -1; } else { return ccode + 1; } -#endif } -// decode string into lat,lon; returns negative in case of error +// PUBLIC - decode string into lat,lon; returns negative in case of error int decodeMapcodeToLatLon(double *lat, double *lon, const char *input, int context_tc) // context_tc is used to disambiguate ambiguous short mapcode inputs; pass 0 or negative if not available { - if (lat == NULL || lon == NULL || input == NULL) { + if ((lat == NULL) || (lon == NULL) || (input == NULL)) { return -100; } else { @@ -2177,78 +2144,7 @@ int decodeMapcodeToLatLon(double *lat, double *lon, const char *input, } } -#ifdef SUPPORT_FOREIGN_ALPHABETS - -// convert as much as will fit of mapcode into unibuf -UWORD *convertToAlphabet(UWORD *unibuf, int maxlength, const char *mapcode, int alphabet) // 0=roman, 2=cyrillic -{ - UWORD *startbuf = unibuf; - UWORD *lastspot = &unibuf[maxlength - 1]; - if (maxlength>0) { - char u[MAX_MAPCODE_RESULT_LEN]; - - // skip leading spaces - while (*mapcode > 0 && *mapcode <= 32) { mapcode++; } - - // straight-copy everything up to and including first space - { - const char *e = strchr(mapcode, ' '); - if (e) { - while (mapcode <= e) { - if (unibuf == lastspot) { // buffer fully filled? - // zero-terminate and return - *unibuf = 0; - return startbuf; - } - *unibuf++ = *mapcode++; - } - } - } - - // re-pack E/U-voweled mapcodes when necessary: - if (asc2lan[alphabet][4] == 0x003f) { // alphabet has no letter E - if (strchr(mapcode, 'E') || strchr(mapcode, 'U') || - strchr(mapcode, 'e') || strchr(mapcode, 'u')) - { - // copy trimmed mapcode into temporary buffer u - int len = (int) strlen(mapcode); - if (len > MAX_MAPCODE_RESULT_LEN - 1) { - len = MAX_MAPCODE_RESULT_LEN - 1; - } - while (len>0 && mapcode[len-1]>0 && mapcode[len-1]<=32) { len--; } - memcpy(u, mapcode, len); - u[len] = 0; - // re-pack into A-voweled mapcode - unpack_if_alldigits(u); - repack_if_alldigits(u, 1); - mapcode = u; - } - } - encode_utf16(unibuf, 1 + (int)(lastspot - unibuf), mapcode, alphabet); - } - return startbuf; -} - -// Legacy: NOT threadsafe -static char asciibuf[MAX_MAPCODE_RESULT_LEN]; - -const char *decodeToRoman(const UWORD *s) { - return convertToRoman(asciibuf, MAX_MAPCODE_RESULT_LEN, s); -} - -// Legacy: NOT threadsafe -static int debugStopAt = -1; // to externally test-restrict internal encoding, do not use! - -// Legacy: NOT threadsafe -static UWORD unibuf[MAX_MAPCODE_RESULT_LEN]; - -const UWORD *encodeToAlphabet(const char *mapcode, int alphabet) // 0=roman, 2=cyrillic -{ - return convertToAlphabet(unibuf, MAX_MAPCODE_RESULT_LEN, mapcode, alphabet); -} - -#endif - +// PUBLIC - encode lat,lon for (optional) TerritoryCode tc to a mapcode with extraDigits accuracy int encodeLatLonToSingleMapcode(char *result, double lat, double lon, int tc, int extraDigits) { char *v[2]; Mapcodes rlocal; @@ -2266,11 +2162,17 @@ int encodeLatLonToSingleMapcode(char *result, double lat, double lon, int tc, in return 1; } -// Threadsafe +// PUBLIC - encode lat,lon for (optional) TerritoryCode tc to mapcodes with extraDigits accuracy int encodeLatLonToMapcodes(Mapcodes *results, double lat, double lon, int territoryCode, int extraDigits) { return encodeLatLonToMapcodes_internal(NULL, results, lat, lon, territoryCode, 0, debugStopAt, extraDigits); } +/////////////////////////////////////////////////////////////////////////////////////////////// +// +// LEGACY ROUTINES (NOT THREADSAFE) +// +/////////////////////////////////////////////////////////////////////////////////////////////// + // Legacy: NOT threadsafe Mapcodes rglobal; @@ -2281,7 +2183,6 @@ int encodeLatLonToMapcodes_Deprecated(char **v, double lat, double lon, int terr // Legacy: NOT threadsafe static char makeiso_bufbytes[16]; static char *makeiso_buf; - const char *convertTerritoryCodeToIsoName(int tc, int format) { if (makeiso_buf == makeiso_bufbytes) { makeiso_buf = makeiso_bufbytes + 8; @@ -2289,54 +2190,21 @@ const char *convertTerritoryCodeToIsoName(int tc, int format) { return (const char *) getTerritoryIsoName(makeiso_buf, tc, format); } -// maximum error in meters for a certain nr of high-precision digits -static const double maxErrorInMetersForDigits[MAX_PRECISION_DIGITS + 1] = { - 7.49, - 1.39, - 0.251, - 0.0462, - 0.00837, - 0.00154, - 0.00028, - 0.000052, - 0.0000093 -}; +#ifdef SUPPORT_FOREIGN_ALPHABETS -double maxErrorInMeters(int extraDigits) { - if ((extraDigits<0) || (extraDigits>MAX_PRECISION_DIGITS)) - return (double)0; - return maxErrorInMetersForDigits[extraDigits]; +// Legacy: NOT threadsafe +static char asciibuf[MAX_MAPCODE_RESULT_LEN]; + +const char *decodeToRoman(const UWORD *s) { + return convertToRoman(asciibuf, MAX_MAPCODE_RESULT_LEN, s); } -// returns nonzero if coordinate is near more than one territory border -int multipleBordersNearby(double lat, double lon, int territoryCode) { - const int ccode = territoryCode - 1; - if ((ccode >= 0) && (ccode < ccode_earth)) { // valid territory, not earth - const int parentTerritoryCode = getParentCountryOf(territoryCode); - if (parentTerritoryCode >= 0) { - // there is a parent! check its borders as well... - if (multipleBordersNearby(lat, lon, parentTerritoryCode)) { - return 1; - } - } - { - int m; - int nrFound = 0; - const int from = firstrec(ccode); - const int upto = lastrec(ccode); - point32 coord32; - convertCoordsToMicrosAndFractions(&coord32, NULL, NULL, lat, lon); - for (m = upto; m >= from; m--) { - if (!isRestricted(m)) { - if (isNearBorderOf(&coord32, m)) { - nrFound++; - if (nrFound > 1) { - return 1; - } - } - } - } - } - } - return 0; +// Legacy: NOT threadsafe +static UWORD unibuf[MAX_MAPCODE_RESULT_LEN]; + +const UWORD *encodeToAlphabet(const char *mapcode, int alphabet) // 0=roman, 2=cyrillic +{ + return convertToAlphabet(unibuf, MAX_MAPCODE_RESULT_LEN, mapcode, alphabet); } + +#endif diff --git a/mapcodelib/mapcoder.h b/mapcodelib/mapcoder.h index b59d857..ea127ff 100644 --- a/mapcodelib/mapcoder.h +++ b/mapcodelib/mapcoder.h @@ -245,30 +245,22 @@ double distanceInMeters(double latDeg1, double lonDeg1, double latDeg2, double l double maxErrorInMeters(int extraDigits); /** - * Is coordinate within a given territory? + * Is coordinate near more than one territory border? * * Arguments: * lat - Latitude, in degrees. Range: -90..90. * lon - Longitude, in degrees. Range: -180..180. * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode) * - * isInsideTerritory returns nonzero if the coordinate is inside the specified territory. - * - * isFullyInsideTerritory returns nonzero if the coordinate is inside the territory, - * and, IF the territory has a perent territory, inside the parent territory. + * returns nonzero if coordinate is near more than one territory border * * Note that for the mapcode system, the following should hold: IF a point p has a * mapcode M, THEN decode(M) delivers a point q within maxErrorInMeters() of p. - * Furthermore, encode(q) must yield back M *unless* point q is not "fully inside" - * the mapcode territory. + * Furthermore, encode(q) must yield back M *unless* point q is near multiple borders. */ -int isInsideTerritory( - double lat, - double lon, - int territoryCode); -int isFullyInsideTerritory( - double lat, - double lon, +int multipleBordersNearby( + double lat, + double lon, int territoryCode); /** diff --git a/unitttest/unittest.c b/unitttest/unittest.c index f87a19e..5b438dd 100644 --- a/unitttest/unittest.c +++ b/unitttest/unittest.c @@ -179,7 +179,8 @@ static void testEncodeAndDecode(const char *str, double y, double x, int localso nrTests++; if (nrresults != localsolutions) { nrErrors++; - printf("*** ERROR *** encode(%0.8f , %0.8f,%d) does not deliver %d local solutions\n", y, x, tc, localsolutions); + printf("*** ERROR *** encode(%0.8f , %0.8f,%d) does not deliver %d local solutions\n", + y, x, tc, localsolutions); printGeneratedMapcodes("Delivered", &mapcodes); } @@ -214,7 +215,7 @@ static void testEncodeAndDecode(const char *str, double y, double x, int localso } // test all global solutions at all precisions... - for (precision = 0; precision <= 8; precision++) { + for (precision = 0; precision <= 8; precision++) { nrresults = encodeLatLonToMapcodes(&mapcodes, y, x, 0, precision); for (i = 0; i < nrresults; i++) { const char *str = mapcodes.mapcode[i]; @@ -240,7 +241,7 @@ static void testEncodeAndDecode(const char *str, double y, double x, int localso else { Mapcodes mapcodesTerritory; Mapcodes mapcodesParent; - int tc2 = -1, tcParent = -1, j, found = 0; + int tc2 = -1, tcParent = -1, j, found = 0; char *e = strchr(str, ' '); if (e) { *e = 0; @@ -273,9 +274,10 @@ static void testEncodeAndDecode(const char *str, double y, double x, int localso } if (!found) { // within 7.5 meters, but not reproduced! - if ( ! multipleBordersNearby(lat, lon, tc2) ) { // but SHOULD be reproduced! + if (!multipleBordersNearby(lat, lon, tc2)) { // but SHOULD be reproduced! nrErrors++; - printf("*** ERROR *** %s does not re-encode (%0.15f,%0.15f) from (%0.15f,%0.15f)\n", str, lat, lon, y, x); + printf("*** ERROR *** %s does not re-encode (%0.15f,%0.15f) from (%0.15f,%0.15f)\n", + str, lat, lon, y, x); printGeneratedMapcodes("Global ", &mapcodes); printGeneratedMapcodes("Territory", &mapcodesTerritory); if (tcParent >= 0) { @@ -393,13 +395,13 @@ void test_territory(const char *alphaCode, int tc, int isAlias, int needsParent, char alphacode[8]; int tn; strcpy(alphacode, alphaCode); - if (!needsParent && i==0) { + if (!needsParent && (i == 0)) { tn = convertTerritoryIsoNameToCode(alphacode, 0); nrTests++; if (tn != tc) { nrErrors++; - printf("*** ERROR *** convertTerritoryIsoNameToCode('%s')=%d but expected %d (%s)\n", - alphacode, tn, tc, convertTerritoryCodeToIsoName(tc,0) ); + printf("*** ERROR *** convertTerritoryIsoNameToCode('%s')=%d but expected %d (%s)\n", + alphacode, tn, tc, convertTerritoryCodeToIsoName(tc, 0)); } } alphacode[i] = (char) tolower(alphacode[i]); @@ -482,46 +484,45 @@ static void re_encode_tests() { } -void distance_tests() -{ - if (strcmp(mapcode_cversion,"2.1.3") >=0) { +void distance_tests() { + if (strcmp(mapcode_cversion, "2.1.3") >= 0) { int i; double coordpairs[] = { - // lat1, lon1, lat2, lon2, expected distance * 100000 - 1,1,1,1,0, - 0,0,0,1,11131949079, - 89,0,89,1,194279300, - 3,0,3,1,11116693130, - -3,0,-3,1,11116693130, - -3,-179.5,-3,179.5,11116693130, - -3,179.5,-3,-179.5,11116693130, - 3,8,3,9,11116693130, - 3,-8,3,-9,11116693130, - 3,-0.5,3,0.5,11116693130, - 54,5,54.000001,5,11095, - 54,5,54,5.000001,6543, - 54,5,54.000001,5.000001,12880, - 90,0,90,50,0, - 0.11,0.22,0.12,0.2333,185011466, - -1 + // lat1, lon1, lat2, lon2, expected distance * 100000 + 1, 1, 1, 1, 0, + 0, 0, 0, 1, 11131949079, + 89, 0, 89, 1, 194279300, + 3, 0, 3, 1, 11116693130, + -3, 0, -3, 1, 11116693130, + -3, -179.5, -3, 179.5, 11116693130, + -3, 179.5, -3, -179.5, 11116693130, + 3, 8, 3, 9, 11116693130, + 3, -8, 3, -9, 11116693130, + 3, -0.5, 3, 0.5, 11116693130, + 54, 5, 54.000001, 5, 11095, + 54, 5, 54, 5.000001, 6543, + 54, 5, 54.000001, 5.000001, 12880, + 90, 0, 90, 50, 0, + 0.11, 0.22, 0.12, 0.2333, 185011466, + -1 }; - - for(i=0;coordpairs[i]!=-1;i+=5) { - const double distance = distanceInMeters( - coordpairs[i],coordpairs[i+1], - coordpairs[i+2],coordpairs[i+3]); - nrTests++; - if ( floor(0.5+(100000.0 * distance)) != coordpairs[i+4] ) { - nrErrors++; - printf("*** ERROR *** distanceInMeters %d failed: %f\n",i,distance);; - } - } - } + + for (i = 0; coordpairs[i] != -1; i += 5) { + const double distance = distanceInMeters( + coordpairs[i], coordpairs[i + 1], + coordpairs[i + 2], coordpairs[i + 3]); + nrTests++; + if (floor(0.5 + (100000.0 * distance)) != coordpairs[i + 4]) { + nrErrors++; + printf("*** ERROR *** distanceInMeters %d failed: %f\n", i, distance);; + } + } + } } void test_territory_insides() { - if (strcmp(mapcode_cversion,"2.1.5") >=0) { + if (strcmp(mapcode_cversion, "2.1.5") >= 0) { int i; struct { const char *territory; @@ -529,55 +530,55 @@ void test_territory_insides() { double lon; int nearborders; } iTestData[] = { - {"AAA", 0, 0,0}, - {"AAA", 0, 999,0}, - {"AAA", 90, 0,0}, - {"AAA", -90, 0,0}, - {"AAA", 0, 180,0}, - {"AAA", 0, -180,0}, - {"ATA", -90, 0,1}, // ATA -90,0 has 2 borders as of data version 2.2 - {"ATA", -70, 0,0}, - - {"USA", 31, -70,0}, // interational waters (not in state) - {"MEX", 19,-115,0}, // interational waters (not in state) - {"MEX", 18.358525, -114.722672,0}, // Isla Clarion, not in a state - {"MX-ROO", 20, -87,0}, // just in ROO - {"MX-ROO", 20,-87.3,0}, // in ROO because in MEX - {"MEX", 20,-87.3,0}, // in ROO because in MEX - - {"IND", 19, 87, 0}, - - {"NLD", 52.6, 4.8,0}, - {"US-WV", 40.18, -80.87,0}, - {"USA", 40.18, -80.87,0}, - {"US-FL", 24.7, -82.7,0}, - {"USA", 24.7, -82.7,0}, - {"IN-TG", 16.13, 78.75,0}, - {"IN-AP", 16.13, 78.75,0}, - {"IN-MH", 16.13, 78.75,0}, - {"IN-PY", 16.13, 78.75,0}, - {"IND", 16.13, 78.75,0}, - {"USA", 40.7, -74,0}, - - {"US-NY", 40.7, -74,1}, - {"MEX", 20.252060, -89.779821,1}, - {"NLD", 52.467314, 4.494037,1}, - {"MEX",21.431778909671 , -89.779828861356,1}, - {"MEX",21.431788272457 , -89.779820144176,1}, - - {NULL} + {"AAA", 0, 0, 0}, + {"AAA", 0, 999, 0}, + {"AAA", 90, 0, 0}, + {"AAA", -90, 0, 0}, + {"AAA", 0, 180, 0}, + {"AAA", 0, -180, 0}, + {"ATA", -90, 0, 1}, // ATA -90,0 has 2 borders as of data version 2.2 + {"ATA", -70, 0, 0}, + + {"USA", 31, -70, 0}, // interational waters (not in state) + {"MEX", 19, -115, 0}, // interational waters (not in state) + {"MEX", 18.358525, -114.722672, 0}, // Isla Clarion, not in a state + {"MX-ROO", 20, -87, 0}, // just in ROO + {"MX-ROO", 20, -87.3, 0}, // in ROO because in MEX + {"MEX", 20, -87.3, 0}, // in ROO because in MEX + + {"IND", 19, 87, 0}, + + {"NLD", 52.6, 4.8, 0}, + {"US-WV", 40.18, -80.87, 0}, + {"USA", 40.18, -80.87, 0}, + {"US-FL", 24.7, -82.7, 0}, + {"USA", 24.7, -82.7, 0}, + {"IN-TG", 16.13, 78.75, 0}, + {"IN-AP", 16.13, 78.75, 0}, + {"IN-MH", 16.13, 78.75, 0}, + {"IN-PY", 16.13, 78.75, 0}, + {"IND", 16.13, 78.75, 0}, + {"USA", 40.7, -74, 0}, + + {"US-NY", 40.7, -74, 1}, + {"MEX", 20.252060, -89.779821, 1}, + {"NLD", 52.467314, 4.494037, 1}, + {"MEX", 21.431778909671, -89.779828861356, 1}, + {"MEX", 21.431788272457, -89.779820144176, 1}, + + {NULL} }; - for (i = 0; iTestData[i].territory != NULL; i++) { - int territory = convertTerritoryIsoNameToCode(iTestData[i].territory,0); - nrTests++; - if (multipleBordersNearby(iTestData[i].lat, iTestData[i].lon, territory) != iTestData[i].nearborders) { - nrErrors++; + for (i = 0; iTestData[i].territory != NULL; i++) { + int territory = convertTerritoryIsoNameToCode(iTestData[i].territory, 0); + nrTests++; + if (multipleBordersNearby(iTestData[i].lat, iTestData[i].lon, territory) != iTestData[i].nearborders) { + nrErrors++; printf("*** ERROR *** multipleBordersNearby(%+18.13f,%+18.13f, \"%s\") not %d\n", - iTestData[i].lat, iTestData[i].lon, iTestData[i].territory, iTestData[i].nearborders); - } - } - } + iTestData[i].lat, iTestData[i].lon, iTestData[i].territory, iTestData[i].nearborders); + } + } + } } @@ -601,7 +602,7 @@ void main() { printf("-----------------------------------------------------------\nFailing decode tests\n"); test_failing_decodes(); - + printf("-----------------------------------------------------------\nFailing decodes tests\n"); test_failing_decodes(); From 02f39ca15754c057e94263b5e79651032ed675da Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Tue, 8 Sep 2015 15:59:49 +0200 Subject: [PATCH 2/6] Extended power of getTerritoryCode --- mapcodelib/mapcoder.c | 38 ++++++++++++++++++++++++-------------- mapcodelib/mapcoder.h | 22 +++++++++++----------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/mapcodelib/mapcoder.c b/mapcodelib/mapcoder.c index 7f5bf56..1313786 100644 --- a/mapcodelib/mapcoder.c +++ b/mapcodelib/mapcoder.c @@ -1494,14 +1494,14 @@ static int decoderEngine(decodeRec *dec) { if (s) { *s++ = 0; while (*s > 0 && *s <= 32) { s++; } - ccode = convertTerritoryIsoNameToCode(w, dec->context - 1) - 1; + ccode = getTerritoryCode(w, dec->context - 1) - 1; } else { ccode = dec->context - 1; s = w; } if (ccode == ccode_mex && len < 8) { - ccode = convertTerritoryIsoNameToCode("5MX", -1) - 1; + ccode = getTerritoryCode("5MX", -1) - 1; } // special case for mexico country vs state if ((*s == 'u') || (*s == 'U')) { strcpy(s, s + 1); @@ -2074,14 +2074,20 @@ static int cmp_alphacode(const void *e1, const void *e2) { static int binfindmatch(const int parentcode, const char *str) { // build a 4-letter uppercase search term char tmp[5]; + const char *r = str; + int len = 0; + if (parentcode < 0) { return -1; } if (parentcode > 0) { - tmp[0] = (char) ('0' + parentcode); - memcpy(tmp + 1, str, 3); - } else { - memcpy(tmp, str, 4); + tmp[len++] = (char) ('0' + parentcode); } - tmp[4] = 0; + while ((len < 4) && (*r > 32)) { + tmp[len++] = *r++; + } + if (*r > 32) { + return -1; + } + tmp[len] = 0; makeupper(tmp); { // binary-search the result const alphaRec *p; @@ -2101,20 +2107,20 @@ static int binfindmatch(const int parentcode, const char *str) { // PUBLIC - returns territoryCode of string (or negative if not found). // optional_tc: context territoryCode to handle ambiguities (pass <=0 if unknown). -int convertTerritoryIsoNameToCode(const char *string, int optional_tc) +int getTerritoryCode(const char *string, int optional_tc) { - const int ccode = optional_tc - 1; if (string == NULL) { return -1; } while (*string > 0 && *string <= 32) { string++; } // skip leading whitespace if (string[0] && string[1]) { + const int ccode = optional_tc - 1; if (string[2] == '-') { return binfindmatch(getParentcode(string, 2), string + 3); } else if (string[2] && string[3] == '-') { return binfindmatch(getParentcode(string, 3), string + 4); - } else if (optional_tc > 0) { - int parentcode = parentnumber[ccode]; - int b = binfindmatch(parentcode, string); + } else { + const int parentcode = ccode<0 ? 0 : ((parentnumber[ccode] > 0) ? parentnumber[ccode] : parentnumber[ParentTerritoryOf(ccode)]); + const int b = binfindmatch(parentcode, string); if (b > 0) { return b; } // @@ -2144,11 +2150,15 @@ int decodeMapcodeToLatLon(double *lat, double *lon, const char *input, } } -// PUBLIC - encode lat,lon for (optional) TerritoryCode tc to a mapcode with extraDigits accuracy +// PUBLIC - encode lat,lon for TerritoryCode tc to a mapcode with extraDigits accuracy int encodeLatLonToSingleMapcode(char *result, double lat, double lon, int tc, int extraDigits) { char *v[2]; Mapcodes rlocal; - const int ret = encodeLatLonToMapcodes_internal(v, &rlocal, lat, lon, tc, 1, debugStopAt, extraDigits); + int ret; + if (tc <= 0) { + return 0; + } + ret = encodeLatLonToMapcodes_internal(v, &rlocal, lat, lon, tc, 1, debugStopAt, extraDigits); *result = 0; if (ret <= 0) { // no solutions? return -1; diff --git a/mapcodelib/mapcoder.h b/mapcodelib/mapcoder.h index ea127ff..880143a 100644 --- a/mapcodelib/mapcoder.h +++ b/mapcodelib/mapcoder.h @@ -18,7 +18,7 @@ extern "C" { #endif -#define mapcode_cversion "2.2" +#define mapcode_cversion "2.2.1" #define UWORD unsigned short int // 2-byte unsigned integer. @@ -52,7 +52,7 @@ typedef struct { * mapcodes - a pointer to an Mapcodes, allocated by the caller. * lat - Latitude, in degrees. Range: -90..90. * lon - Longitude, in degrees. Range: -180..180. - * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode), used as encoding context. + * territoryCode - Territory code (obtained from getTerritoryCode), used as encoding context. * Pass 0 to get Mapcodes for all territories. * extraDigits - Number of extra "digits" to add to the generated mapcode. The preferred default is 0. * Other valid values are 1 and 2, which will add extra letters to the mapcodes to @@ -84,7 +84,7 @@ int encodeLatLonToMapcodes( * by the next call to this method! * lat - Latitude, in degrees. Range: -90..90. * lon - Longitude, in degrees. Range: -180..180. - * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode), used as encoding context. + * territoryCode - Territory code (obtained from getTerritoryCode), used as encoding context. * Pass 0 to get Mapcodes for all territories. * extraDigits - Number of extra "digits" to add to the generated mapcode. The preferred default is 0. * Other valid values are 1 and 2, which will add extra letters to the mapcodes to @@ -113,8 +113,7 @@ int encodeLatLonToMapcodes_Deprecated( // Warning: this method is deprecated * The caller should allocate at least MAX_MAPCODE_RESULT_LEN characters for the string. * lat - Latitude, in degrees. Range: -90..90. * lon - Longitude, in degrees. Range: -180..180. - * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode), used as encoding context. - * Pass 0 to get the shortest Mapcode for all territories. + * territoryCode - Territory code (obtained from getTerritoryCode), used as encoding context. * extraDigits - Number of extra "digits" to add to the generated mapcode. The preferred default is 0. * Other valid values are 1 and 2, which will add extra letters to the mapcodes to * make them represent the coordinate more accurately. @@ -136,7 +135,7 @@ int encodeLatLonToSingleMapcode( * lat - Decoded latitude, in degrees. Range: -90..90. * lon - Decoded longitude, in degrees. Range: -180..180. * mapcode - Mapcode to decode. - * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode), used as decoding context. + * territoryCode - Territory code (obtained from getTerritoryCode), used as decoding context. * Pass 0 if not available. * * Returns: @@ -169,14 +168,14 @@ int compareWithMapcodeFormat( * Convert a territory name to a territory code. * * Arguments: - * isoNam - Territory name to convert. + * string - String starting with ISO code of territory (e.g. "USA" or "US-CA"). * parentTerritoryCode - Parent territory code, or 0 if not available. * * Returns: * Territory code >0 if succeeded, or <0 if failed. */ -int convertTerritoryIsoNameToCode( - const char *isoName, +int getTerritoryCode( + const char *string, int parentTerritoryCode); /** @@ -250,7 +249,7 @@ double maxErrorInMeters(int extraDigits); * Arguments: * lat - Latitude, in degrees. Range: -90..90. * lon - Longitude, in degrees. Range: -180..180. - * territoryCode - Territory code (obtained from convertTerritoryIsoNameToCode) + * territoryCode - Territory code (obtained from getTerritoryCode) * * returns nonzero if coordinate is near more than one territory border * @@ -332,11 +331,12 @@ const UWORD *encodeToAlphabet(const char *string, int alphabet); /** * List of #defines to support legacy systems. */ +#define convertTerritoryIsoNameToCode getTerritoryCode #define coord2mc(results, lat, lon, territoryCode) encodeLatLonToMapcodes_Deprecated(results, lat, lon,territoryCode, 0) #define coord2mc1(results, lat, lon, territoryCode) encodeLatLonToSingleMapcode(results, lat, lon, territoryCode, 0) #define mc2coord decodeMapcodeToLatLon #define lookslikemapcode compareWithMapcodeFormat -#define text2tc convertTerritoryIsoNameToCode +#define text2tc getTerritoryCode #define tc2text convertTerritoryCodeToIsoName #define tccontext getCountryOrParentCountry #define tcparent getParentCountryOf From 62b23fc0f5f18ca5091d5f945fd69aed4cfcb2e6 Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Tue, 8 Sep 2015 16:00:02 +0200 Subject: [PATCH 3/6] 2.2 Updated unit test --- unitttest/unittest.c | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/unitttest/unittest.c b/unitttest/unittest.c index 5b438dd..e58d27f 100644 --- a/unitttest/unittest.c +++ b/unitttest/unittest.c @@ -581,6 +581,50 @@ void test_territory_insides() { } } +void territory_code_tests() { + int i; + + static const struct { + int expectedresult; + int context; + const char *inputstring; + } tcTestData[] = { + {319, 0, "AL"}, // + {483,497, "AL"}, // 497=rus + {483,431, "AL"}, // 431=ru-tam + {365,411, "AL"}, // 411=usa + {365,392, "AL"}, // 392=us-ca + { -1, 0, ""}, + { -1, 0, "R"}, + { -1, 0, "RX"}, + { -1, 0, "RXX"}, + {497, 0, "RUS"}, + { -1, 0, "RUSSIA"}, + {411, 0, "US"}, + {411, 0, "USA"}, + {411, 0, "usa"}, + { -1, 0, "US-TEST"}, + {411, 0, "US TEST"}, + {392, 0, "US-CA"}, + {392, 0, "US-CA TEST"}, + {392, 0, "USA-CA"}, + {431, 0, "RUS-TAM"}, + { -1, 0, "RUS-TAMX"}, + {431, 0, "RUS-TAM X"}, + {0,0,NULL} + }; + + for (i = 0; tcTestData[i].inputstring!=NULL; i++ ) { + int tc = getTerritoryCode(tcTestData[i].inputstring, tcTestData[i].context); + nrTests++; + if (tc != tcTestData[i].expectedresult) { + nrErrors++; + printf("*** ERROR *** getTerritoryCode(%s)=%d, expected %d\n", + tcTestData[i].inputstring, tc, tcTestData[i].expectedresult); + } + } +} + void main() { #ifdef XSIDE3 @@ -598,6 +642,7 @@ void main() { printf("-----------------------------------------------------------\nTerritory tests\n"); printf("%d territories\n", MAX_CCODE); test_territories(); + territory_code_tests(); test_territory_insides(); printf("-----------------------------------------------------------\nFailing decode tests\n"); From 15cb12bbc9931ca37ab783b08a1c3f8c9e59337f Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Tue, 8 Sep 2015 16:00:35 +0200 Subject: [PATCH 4/6] 2.2 Updated documentation --- mapcode_library_c.doc | Bin 122880 -> 155136 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/mapcode_library_c.doc b/mapcode_library_c.doc index 46722aa4fbb07dd94c15d6c1a80c239960636448..99d19b17124ff853d83772b2fea6859aaf91a2da 100644 GIT binary patch literal 155136 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 literal 122880 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 Date: Tue, 8 Sep 2015 16:10:18 +0200 Subject: [PATCH 5/6] 2.2. Updated unit test --- unitttest/unittest.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/unitttest/unittest.c b/unitttest/unittest.c index e58d27f..f40cb0e 100644 --- a/unitttest/unittest.c +++ b/unitttest/unittest.c @@ -589,11 +589,6 @@ void territory_code_tests() { int context; const char *inputstring; } tcTestData[] = { - {319, 0, "AL"}, // - {483,497, "AL"}, // 497=rus - {483,431, "AL"}, // 431=ru-tam - {365,411, "AL"}, // 411=usa - {365,392, "AL"}, // 392=us-ca { -1, 0, ""}, { -1, 0, "R"}, { -1, 0, "RX"}, @@ -611,6 +606,11 @@ void territory_code_tests() { {431, 0, "RUS-TAM"}, { -1, 0, "RUS-TAMX"}, {431, 0, "RUS-TAM X"}, + {319, 0, "AL"}, // + {483,497, "AL"}, // 497=rus + {483,431, "AL"}, // 431=ru-tam + {365,411, "AL"}, // 411=usa + {365,392, "AL"}, // 392=us-ca {0,0,NULL} }; @@ -619,8 +619,9 @@ void territory_code_tests() { nrTests++; if (tc != tcTestData[i].expectedresult) { nrErrors++; - printf("*** ERROR *** getTerritoryCode(%s)=%d, expected %d\n", - tcTestData[i].inputstring, tc, tcTestData[i].expectedresult); + printf("*** ERROR *** getTerritoryCode(\"%s\", %d)=%d, expected %d\n", + tcTestData[i].inputstring, tcTestData[i].context, + tc, tcTestData[i].expectedresult); } } } From c4c4971c6d3a667b7f2db9a6d318d399ea233ff6 Mon Sep 17 00:00:00 2001 From: Mapcode C Developer Date: Tue, 8 Sep 2015 17:25:32 +0200 Subject: [PATCH 6/6] 2.2.1 Updated version number --- unitttest/unittest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unitttest/unittest.c b/unitttest/unittest.c index f40cb0e..661925d 100644 --- a/unitttest/unittest.c +++ b/unitttest/unittest.c @@ -14,7 +14,7 @@ * limitations under the License. */ -#define UNITTEST_VERSION "2.2" +#define UNITTEST_VERSION "2.2.1" /** * This application performs a number of tests on the Mapcode C library.