Commit 3ac93a19 authored by Stefano Petrilli's avatar Stefano Petrilli Committed by Sergei Golubchik

MDEV-34159: Implements ST_LatFromGeoHash

The GIS function ST_LatFromGeoHash takes in input a geohash and returns
its latitude.
The latitude is returned as a numeric value in the interval [90, -90].
If the argument is NULL, the return value is NULL. If the argument is
invalid, an ER_INCORRECT_TYPE is thrown.

Author: StefanoPetrilli <stefanop_1999@hotmail.it>
Co-authored-by: default avatarkevincheng2 <chengyf112@gmail.com>
Co-authored-by: default avatarCatalin Besleaga <catalin.besleaga@oracle.com>
Co-authored-by: default avatarGleb Shchepa <gleb.shchepa@oracle.com>
Co-authored-by: default avatarTatiana Azundris Nuernberg <tatjana.nuernberg@oracle.com>
Co-authored-by: default avatarMartin Hansson <martin.hansson@oracle.com>
Co-authored-by: default avatarDeepa Dixit <deepa.dixit@oracle.com>
Co-authored-by: default avatarHans H Melby <hans.h.melby@oracle.com>
Co-authored-by: default avatarJens Even Berg Blomsøy <jens.even.blomsoy@oracle.com>
Co-authored-by: default avatarErlend Dahl <erlend.dahl@oracle.com>
Co-authored-by: default avatarNorvald H. Ryeng <norvald.ryeng@oracle.com>
Co-authored-by: default avatarBennyWang <benny.wang@oracle.com>
Co-authored-by: default avatarDavid.Zhao <david.zhao@oracle.com>
Co-authored-by: default avatarErik Froseth <erik.froseth@oracle.com>
parent 64e235d8
......@@ -17,6 +17,213 @@
-- disable_warnings
# Table with different extreme values
CREATE TABLE geohashes (gid INT NOT NULL PRIMARY KEY, hash_value VARCHAR(255));
INSERT INTO geohashes VALUES
(1, "000000000000000000000"),
(2, "zzzzzzzzzzzzzzzzzzzzz"),
(3, NULL),
(4, "s00t"),
(5, "7zzzm"),
(6, "s00d"),
(7, "0"),
(8, "z"),
(9, "3ejh6z75ddt2d839zh2u"),
(10, "twtsuqg3q7vh3nrbt0nn"),
(11, "yw8s10dxddhe4s06nsph"),
(12, "h4g4h9yrjtgzvewxm0ru"),
(13, "9kqbredcnhq1b44ue48s"),
(14, "1pckwjkqw3km0v6ye5d2"),
(15, "wm313fnr92ggsysm64e6"),
(16, "vqghx20fx6d8r5vfkbgf"),
(17, "wvetm3u23kr9r6663k31"),
(18, "e5t2p7sk291vpyb08pwu");
#####################################################################
# ST_LATFROMGEOHASH()
#####################################################################
# Check for all valid characters and inputs
--echo # valid characters
SELECT ST_LATFROMGEOHASH("0");
SELECT ST_LATFROMGEOHASH("z");
SELECT ST_LATFROMGEOHASH("0z");
SELECT ST_LATFROMGEOHASH("xbpb");
SELECT ST_LATFROMGEOHASH("8000");
SELECT ST_LATFROMGEOHASH("s000");
SELECT ST_LATFROMGEOHASH("0123456789");
SELECT ST_LATFROMGEOHASH("9876543210");
SELECT ST_LATFROMGEOHASH("bcdefghjkmnpqrstuvwxyz");
SELECT ST_LATFROMGEOHASH("zyxwvutsrqpnmkjhgfedcb");
SELECT ST_LATFROMGEOHASH("zzzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("bpbpbpbpbpbpbpbpbpbp");
SELECT ST_LATFROMGEOHASH("pbpbpbpbpbpbpbpbpbpb");
SELECT ST_LATFROMGEOHASH("00000000000000000000");
SELECT ST_LATFROMGEOHASH("gzzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("5bpbpbpbpbpbpbpbpbpb");
SELECT ST_LATFROMGEOHASH("7zzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("rzzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("2pbpbpbpbpbpbpbpbpbp");
SELECT ST_LATFROMGEOHASH("0000000000zzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("zzzzzzzzzz0000000000");
SELECT ST_LATFROMGEOHASH("s000000001z7wsg7zzm6");
SELECT ST_LATFROMGEOHASH("ebpbpbpbpcbe9kuebp6d");
SELECT ST_LATFROMGEOHASH("kpbpbpbpbnpkqe5kpbtm");
SELECT ST_LATFROMGEOHASH("7zzzzzzzzy0s37hs00dt");
SELECT ST_LATFROMGEOHASH("tzzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("9zzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("jzzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("1zzzzzzzzzzzzzzzzzzz");
SELECT ST_LATFROMGEOHASH("zbzurypzpgxczbzurypz");
SELECT ST_LATFROMGEOHASH("5zpgxczbzurypzpgxczb");
SELECT ST_LATFROMGEOHASH("0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z0z");
SELECT ST_LATFROMGEOHASH("0123456789bcdefghjkmnpqrstuvwxyz");
SELECT ST_LATFROMGEOHASH("0123456789BCDEFGHJKMNPQRSTUVWXYZ");
SELECT ST_LATFROMGEOHASH("zyxwvutsrqpnmkjhgfedcb9876543210");
SELECT ST_LATFROMGEOHASH("ZYXWVUTSRQPNMKJHGFEDCB9876543210");
SELECT ST_LATFROMGEOHASH("1e1");
SELECT ST_LATFROMGEOHASH("100");
SELECT ST_LATFROMGEOHASH(CAST(100 AS CHAR));
SELECT ST_LATFROMGEOHASH("10111000110001111001");
SELECT ST_LATFROMGEOHASH("11111111111111111111");
SELECT ST_LATFROMGEOHASH("99999999999999999999");
SELECT ST_LATFROMGEOHASH(NULL);
SELECT ST_LATFROMGEOHASH(null);
SELECT ST_LATFROMGEOHASH(CAST("012" AS BINARY));
# Invalid characters and inputs
--echo # invalid characters and inputs
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("0123a45");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("xyzi");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("zyxLwv");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("bcdjo");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("zyx**wv");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("1 2 3 4");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("1''2345");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("12.345");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH(" ");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("NULL");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("-100");
--error ER_WRONG_VALUE_FOR_TYPE
SELECT ST_LATFROMGEOHASH("");
--error ER_GIS_INVALID_DATA
SELECT ST_LATFROMGEOHASH(9876543210);
--error ER_GIS_INVALID_DATA
SELECT ST_LATFROMGEOHASH(0123456789);
--error ER_GIS_INVALID_DATA
SELECT ST_LATFROMGEOHASH(1e1);
--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT
SELECT ST_LATFROMGEOHASH();
--error ER_WRONG_PARAMCOUNT_TO_NATIVE_FCT
SELECT ST_LATFROMGEOHASH("123","456");
--error ER_PARSE_ERROR
SELECT ST_LATFROMGEOHASH("123",);
--error ER_PARSE_ERROR
SELECT ST_LATFROMGEOHASH(,"456");
--error ER_PARSE_ERROR
SELECT ST_LATFROMGEOHASH(,);
--error ER_PARSE_ERROR
SELECT ST_LATFROMGEOHASH("0123456"789);
--error ER_BAD_FIELD_ERROR
SELECT ST_LATFROMGEOHASH(abcdef);
# Test geohashes that are long
--echo # very long geohash
SELECT ST_LATFROMGEOHASH("0123456789bcdefghjkmnpqrstuvwxyz0123456789bcdefghjkmn"
"pqrstuvwxyz0123456789bcdefghjkmnpqrstuvwxyz0123456789"
"bcdefghjkmnpqrstuvwxyz");
SELECT ST_LATFROMGEOHASH("0123456789BCDEFGHJKMNPQRSTUVWXYZ0123456789BCDEFGHJKMN"
"PQRSTUVWXYZ0123456789BCDEFGHJKMNPQRSTUVWXYZ0123456789"
"BCDEFGHJKMNPQRSTUVWXYZ");
SELECT ST_LATFROMGEOHASH("zyxwvutsrqpnmkjhgfedcb9876543210zyxwvutsrqpnmkjhgfedc"
"b9876543210zyxwvutsrqpnmkjhgfedcb9876543210zyxwvutsrq"
"pnmkjhgfedcb9876543210");
SELECT ST_LATFROMGEOHASH("ZYXWVUTSRQPNMKJHGFEDCB9876543210ZYXWVUTSRQPNMKJHGFEDC"
"B9876543210ZYXWVUTSRQPNMKJHGFEDCB9876543210ZYXWVUTSRQ"
"PNMKJHGFEDCB9876543210");
--echo # different random geohash values
SELECT ST_LATFROMGEOHASH(hash_value) FROM geohashes;
#####################################################################
# ST_GEOHASH()
#####################################################################
......@@ -406,3 +613,5 @@ SELECT ST_GEOHASH(ST_DIFFERENCE(ST_GEOMFROMTEXT('POINT(180 90)'),ST_GEOMFROMTEXT
--error ER_GIS_INVALID_DATA
SELECT ST_GEOHASH(ST_SYMDIFFERENCE(ST_GEOMFROMTEXT('POINT(180 90)'),ST_GEOMFROMTEXT('POINT(0 0)')),20);
--echo # Clean up
DROP TABLE geohashes;
......@@ -2837,6 +2837,226 @@ void Item_func_geohash::set_bit(double &max_value, double &min_value, const doub
}
const uint8_t Item_func_latlongfromgeohash::geohash_alphabet[256] = {
// 0-47
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255,
// '0'-'9' (48-57)
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
// 58-65
255, 255, 255, 255, 255, 255, 255, 255,
// 'B'-'H' (66-73)
10, 11, 12, 13, 14, 15, 16,
// 'I' (74)
255,
// 'J'-'K' (75-76)
17, 18,
// 'L' (77)
255,
// 'M'-'N' (78-79)
19, 20,
// O (80)
255,
// 'P'-'Z' (81-92)
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
255, 255, 255, 255, 255, 255, 255,
// 'b'-'h' (97-104)
10, 11, 12, 13, 14, 15, 16,
// 'i' (105)
255,
// 'j'-'k' (106-107)
17, 18,
// 'l' (108)
255,
// 'm'-'n' (109-110)
19, 20,
// 'o' (111)
255,
// 'p'-'z' (112-122)
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// 123-255
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};
/**
Decodes a geohash string into longitude and latitude.
The results are rounded, based on the length of input geohash. The function
will stop evaluating when the error range, or "accuracy", has become 0.0 for
both latitude and longitude since no more changes can happen after this.
@param geohash The geohash to decode.
@param upper_latitude Upper limit of returned latitude (normally 90.0).
@param[out] result_latitude Calculated latitude.
@param[out] result_longitude Calculated longitude.
@return false on success, true on failure (invalid geohash string).
*/
bool Item_func_latlongfromgeohash::decode_geohash(
String *geohash, double *result_latitude,
double *result_longitude)
{
double latitude_accuracy= (MAX_LATITUDE - MIN_LATITUDE) / 2.0;
double longitude_accuracy= (MAX_LONGITUDE - MIN_LONGITUDE) / 2.0;
double latitude_value= (MAX_LATITUDE + MIN_LATITUDE) / 2.0;
double longitude_value= (MAX_LONGITUDE + MIN_LONGITUDE) / 2.0;
uint number_of_bits_used= 0;
const uint input_length= geohash->length();
for (uint i = 0; i < input_length; i++)
{
int converted_character =
Item_func_latlongfromgeohash::geohash_alphabet[(int) (*geohash)[i]];
if (converted_character == 255) {
return true;
}
for (int bit_number = 4; bit_number >= 0; bit_number--)
{
if (number_of_bits_used % 2) {
latitude_accuracy/= 2.0;
if (converted_character & (1 << bit_number))
latitude_value+= latitude_accuracy;
else
latitude_value-= latitude_accuracy;
} else {
longitude_accuracy/= 2.0;
if (converted_character & (1 << bit_number))
longitude_value+= longitude_accuracy;
else
longitude_value-= longitude_accuracy;
}
number_of_bits_used++;
}
if (latitude_accuracy <= 0.0 || longitude_accuracy <= 0.0)
break;
}
*result_latitude= round_latlongitude(latitude_value, latitude_accuracy * 2.0,
latitude_value - latitude_accuracy,
latitude_value + latitude_accuracy);
*result_longitude= round_latlongitude(longitude_value,
longitude_accuracy * 2.0,
longitude_value - longitude_accuracy,
longitude_value + longitude_accuracy);
return false;
}
/**
Rounds a latitude or longitude value.
This will round a latitude or longitude value, based on error_range.
The error_range is the difference between upper and lower lat/longitude
(e.g upper value of 45.0 and a lower value of 22.5, gives an error range of
22.5).
The returned result will always be in the range [lower_limit, upper_limit]
@param latlongitude The latitude or longitude to round.
@param error_range The total error range of the calculated laglongitude.
@param lower_limit Lower limit of the returned result.
@param upper_limit Upper limit of the returned result.
@return A rounded latitude or longitude.
*/
double Item_func_latlongfromgeohash::round_latlongitude(double latlongitude,
double error_range,
double lower_limit,
double upper_limit)
{
if (error_range == 0.0)
return latlongitude;
uint number_of_decimals= 0;
while (error_range <= 0.1 && number_of_decimals <= DBL_DIG) {
number_of_decimals++;
error_range*= 10.0;
}
double return_value;
do {
return_value= my_double_round(latlongitude, number_of_decimals, false,
false);
number_of_decimals++;
} while ((lower_limit > return_value || return_value > upper_limit) &&
number_of_decimals <= DBL_DIG);
// If the result is outside the allowed range, return the input value
if (lower_limit > return_value || return_value > upper_limit)
return_value= latlongitude;
// Avoid printing signed zero
return return_value + 0.0;
}
bool Item_func_latlongfromgeohash::is_invalid_geohash_field(
enum_field_types field_type)
{
switch (field_type)
{
case MYSQL_TYPE_NULL:
case MYSQL_TYPE_VARCHAR:
return false;
default:
return true;
}
}
double Item_func_latlongfromgeohash::val_real()
{
null_value= 1;
String *input_value;
if (args[0]->null_value)
return 0.0;
if (is_invalid_geohash_field(args[0]->field_type()))
{
my_error(ER_GIS_INVALID_DATA, MYF(0), decode_longitude ?
"ST_LongFromGeoHas" :
"ST_LatFromGeohash");
return 0.0;
}
input_value= args[0]->val_str(&buf);
if (args[0]->null_value)
{
args[0]->null_value= 0;
return 0.0;
}
if (input_value->is_empty())
{
my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "geohash",
input_value->c_ptr_safe(), func_name());
return 0.0;
}
double latitude= 0.0, longitude= 0.0;
if (decode_geohash(input_value, &latitude, &longitude)) {
my_error(ER_WRONG_VALUE_FOR_TYPE, MYF(0), "geohash",
input_value->c_ptr_safe(), func_name());
return 0.0;
}
null_value= 0;
if (decode_longitude) return longitude;
return latitude;
}
String *Item_func_pointonsurface::val_str(String *str)
{
Gcalc_operation_transporter trn(&func, &collector);
......@@ -3280,6 +3500,20 @@ Create_func_geohash::create_native(THD *thd, const LEX_CSTRING *name,
return func;
}
class Create_func_latfromgeohash : public Create_func_arg1
{
public:
Item *create_1_arg(THD *thd, Item *arg1) override
{
return new (thd->mem_root) Item_func_latfromgeohash(thd, arg1);
}
static Create_func_latfromgeohash s_singleton;
protected:
Create_func_latfromgeohash() = default;
virtual ~Create_func_latfromgeohash() = default;
};
class Create_func_endpoint : public Create_func_arg1
{
......@@ -4134,6 +4368,7 @@ Create_func_disjoint Create_func_disjoint::s_singleton;
Create_func_distance Create_func_distance::s_singleton;
Create_func_distance_sphere Create_func_distance_sphere::s_singleton;
Create_func_geohash Create_func_geohash::s_singleton;
Create_func_latfromgeohash Create_func_latfromgeohash ::s_singleton;
Create_func_endpoint Create_func_endpoint::s_singleton;
Create_func_envelope Create_func_envelope::s_singleton;
Create_func_equals Create_func_equals::s_singleton;
......@@ -4258,6 +4493,7 @@ static Native_func_registry func_array_geom[] =
{ { STRING_WITH_LEN("POLYGONFROMTEXT") }, GEOM_BUILDER(Create_func_geometry_from_text)},
{ { STRING_WITH_LEN("POLYGONFROMWKB") }, GEOM_BUILDER(Create_func_geometry_from_wkb)},
{ { STRING_WITH_LEN("GEOHASH") }, GEOM_BUILDER(Create_func_geohash)},
{ { STRING_WITH_LEN("LATFROMGEOHASH") }, GEOM_BUILDER(Create_func_latfromgeohash)},
{ { STRING_WITH_LEN("SRID") }, GEOM_BUILDER(Create_func_srid)},
{ { STRING_WITH_LEN("ST_AREA") }, GEOM_BUILDER(Create_func_area)},
{ { STRING_WITH_LEN("STARTPOINT") }, GEOM_BUILDER(Create_func_startpoint)},
......@@ -4340,6 +4576,7 @@ static Native_func_registry func_array_geom[] =
{ { STRING_WITH_LEN("ST_Y") }, GEOM_BUILDER(Create_func_y)},
{ { STRING_WITH_LEN("ST_DISTANCE_SPHERE") }, GEOM_BUILDER(Create_func_distance_sphere)},
{ { STRING_WITH_LEN("ST_GEOHASH") }, GEOM_BUILDER(Create_func_geohash)},
{ { STRING_WITH_LEN("ST_LATFROMGEOHASH") }, GEOM_BUILDER(Create_func_latfromgeohash)},
{ { STRING_WITH_LEN("TOUCHES") }, GEOM_BUILDER(Create_func_touches)},
{ { STRING_WITH_LEN("WITHIN") }, GEOM_BUILDER(Create_func_within)},
{ { STRING_WITH_LEN("X") }, GEOM_BUILDER(Create_func_x)},
......
......@@ -1251,6 +1251,40 @@ class Item_func_geohash: public Item_geometry_func
};
class Item_func_latlongfromgeohash : public Item_real_func {
private:
String buf;
static const uint8_t geohash_alphabet[256];
const bool decode_longitude;
static bool is_invalid_geohash_field(enum_field_types field_type);
public:
Item_func_latlongfromgeohash(THD *thd, Item *a, bool start_on_even_bit_arg)
: Item_real_func(thd, a),
decode_longitude(start_on_even_bit_arg) {}
double val_real() override;
static bool decode_geohash(String *geohash, double *result_latitude,
double *result_longitude);
static double round_latlongitude(double latlongitude, double error_range,
double lower_limit, double upper_limit);
};
class Item_func_latfromgeohash: public Item_func_latlongfromgeohash
{
public:
Item_func_latfromgeohash(THD *thd, Item *a)
:Item_func_latlongfromgeohash(thd, a, false) {}
LEX_CSTRING func_name_cstring() const override
{
static LEX_CSTRING name= {STRING_WITH_LEN("st_latfromgeohash") };
return name;
}
Item *do_get_copy(THD *thd) const override
{ return get_item_copy<Item_func_latfromgeohash>(thd, this); }
};
class Item_func_pointonsurface: public Item_geometry_func_args_geometry
{
String tmp_value;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment