ABAP实现Geohash

  • 2019 年 10 月 3 日
  • 筆記

前几天群里有人问ABAP有没有Geohash函数,用来帮助SAP存储门店位置、实现查找附近门店的功能。因为没有查到,所以我动手写了一个。

 

Geohash是什么

Geohash是一种公共域地理编码系统,它将一个地理位置编码成一串字母和数字。字符串越长,表示的范围越精确。两个Geohash字符串的相同前缀越多,表示它们所代表的地点的距离越近,这样就可以利用字符串的前缀匹配来快速查询附近的地点信息。

关于Geohash的更多介绍,可以参考:

 

本文链接:https://www.cnblogs.com/hhelibeb/p/11426826.html 

原创内容,转载请注明

实现

我在Github创建了一个repo,包含了自己写的编码、解码方法。地址是:https://github.com/hhelibeb/geohash-abap

代码如下,目前还有更多功能在实现中,有兴趣的朋友可以来一起写。

(注:github上面的代码会不定时更新,博客中贴的只是初始代码,请前往github查看最新代码

class zcl_geohash definition    public    final    create public .      public section.          types: begin of ty_hash,               hash type string,             end of ty_hash.      types: ty_hash_t type standard table of ty_hash with empty key.        types:        ty_tude type p length 16 decimals 12 .        constants c_max_hash_length type i value 12 ##NO_TEXT.        class-methods class_constructor .      class-methods encode        importing          !longitude        type ty_tude          !latitude         type ty_tude          !length           type i default 8        returning          value(r_geo_hash) type string .      class-methods decode        importing          !geohash   type string        exporting          !longitude type ty_tude          !latitude  type ty_tude .        class-methods neighbors importing geohash          type string                              returning value(neighbors) type ty_hash_t.        class-methods validate importing geohash      type string                             returning value(valid) type abap_bool.      private section.        types:        begin of ty_base32,          decimals type i,          base32   type string,        end of ty_base32 .      types:        ty_base32_t1 type hashed table of ty_base32 with unique key decimals .      types:        ty_base32_t2 type hashed table of ty_base32 with unique key base32 .        types: begin of ty_neighbors_odd,               f1 type string,               f2 type string,               f3 type string,               f4 type string,               f5 type string,               f6 type string,               f7 type string,               f8 type string,             end of ty_neighbors_odd.      types: ty_neighbors_odd_t type standard table of ty_neighbors_odd with empty key.        types: begin of ty_neighbors_even,               f1 type string,               f2 type string,               f3 type string,               f4 type string,             end of ty_neighbors_even.      types: ty_neighbors_even_t type standard table of ty_neighbors_even with empty key.        class-data mt_base32_code1 type ty_base32_t1 .      class-data mt_base32_code2 type ty_base32_t2 .        class-data mt_neighbors_odd type ty_neighbors_odd_t.      class-data mt_neighbors_even type ty_neighbors_even_t.        constants c_longitude_min type ty_tude value '-180.00' ##NO_TEXT.      constants c_longitude_max type ty_tude value '180.00' ##NO_TEXT.      constants c_latitude_min type ty_tude value '-90.00' ##NO_TEXT.      constants c_latitude_max type ty_tude value '90.00' ##NO_TEXT.      constants c_zero type c value '0' ##NO_TEXT.      constants c_one type c value '1' ##NO_TEXT.        class-methods bin_to_dec        importing          !i_bin       type string default '0'        returning          value(r_dec) type int4 .      class-methods dec_to_bin        importing          !i_dec       type int4        returning          value(r_bin) type string .        class-methods get_bin        importing          !i_left  type ty_tude          !i_right type ty_tude          !i_tude  type ty_tude        exporting          !e_left  type ty_tude          !e_right type ty_tude          !e_bin   type char1 .      class-methods get_tude        importing          !i_left  type ty_tude          !i_right type ty_tude          !i_bin   type string        exporting          !e_left  type ty_tude          !e_right type ty_tude          !e_tude  type ty_tude .        class-methods: get_index importing index          type i                                         offset         type i                                         max_index      type i                               returning value(r_index) type i.      class-methods: get_code_neighbor importing i_table        type standard table                                                 i_member       type string                                       returning value(r_table) type ty_hash_t.    endclass.

class zcl_geohash implementation.        method bin_to_dec.        if contains( val = i_bin regex = `[^01]` ).        return.      endif.        data(length) = strlen( i_bin ).        data(l_index) = 0.        do length times.          data(temp) = i_bin+l_index(1).          if temp = 1.          r_dec = r_dec + 2 ** ( length - l_index - 1 ).        endif.          l_index = l_index + 1.        enddo.      endmethod.        method class_constructor.        mt_base32_code1 = value #(        ( decimals = 0  base32 = '0' )        ( decimals = 1  base32 = '1' )        ( decimals = 2  base32 = '2' )        ( decimals = 3  base32 = '3' )        ( decimals = 4  base32 = '4' )        ( decimals = 5  base32 = '5' )        ( decimals = 6  base32 = '6' )        ( decimals = 7  base32 = '7' )        ( decimals = 8  base32 = '8' )        ( decimals = 9  base32 = '9' )        ( decimals = 10 base32 = 'b' )        ( decimals = 11 base32 = 'c' )        ( decimals = 12 base32 = 'd' )        ( decimals = 13 base32 = 'e' )        ( decimals = 14 base32 = 'f' )        ( decimals = 15 base32 = 'g' )        ( decimals = 16 base32 = 'h' )        ( decimals = 17 base32 = 'j' )        ( decimals = 18 base32 = 'k' )        ( decimals = 19 base32 = 'm' )        ( decimals = 20 base32 = 'n' )        ( decimals = 21 base32 = 'p' )        ( decimals = 22 base32 = 'q' )        ( decimals = 23 base32 = 'r' )        ( decimals = 24 base32 = 's' )        ( decimals = 25 base32 = 't' )        ( decimals = 26 base32 = 'u' )        ( decimals = 27 base32 = 'v' )        ( decimals = 28 base32 = 'w' )        ( decimals = 29 base32 = 'x' )        ( decimals = 30 base32 = 'y' )        ( decimals = 31 base32 = 'z' )      ).        mt_base32_code2 = mt_base32_code1.        mt_neighbors_odd = value #(       (  f1 = 'b' f2 = 'c' f3 = 'f' f4 = 'g' f5 = 'u' f6 = 'v' f7 = 'y' f8 = 'z' )       (  f1 = '8' f2 = '9' f3 = 'd' f4 = 'e' f5 = 's' f6 = 't' f7 = 'w' f8 = 'x' )       (  f1 = '2' f2 = '3' f3 = '6' f4 = '7' f5 = 'k' f6 = 'm' f7 = 'q' f8 = 'r' )       (  f1 = '0' f2 = '1' f3 = '4' f4 = '5' f5 = 'h' f6 = 'j' f7 = 'n' f8 = 'p' )      ).        mt_neighbors_even = value #(       (  f1 = 'p' f2 = 'r' f3 = 'x' f4 = 'z' )       (  f1 = 'n' f2 = 'q' f3 = 'w' f4 = 'y' )       (  f1 = 'j' f2 = 'm' f3 = 't' f4 = 'v' )       (  f1 = 'h' f2 = 'k' f3 = 's' f4 = 'u' )       (  f1 = '5' f2 = '7' f3 = 'e' f4 = 'g' )       (  f1 = '4' f2 = '6' f3 = 'd' f4 = 'f' )       (  f1 = '1' f2 = '3' f3 = '9' f4 = 'c' )       (  f1 = '0' f2 = '2' f3 = '8' f4 = 'b' )      ).      endmethod.        method decode.        types: numc5 type n length 5.        data(length) = strlen( geohash ).        if length <= 0.        return.      endif.        if length > c_max_hash_length.        length = c_max_hash_length.      endif.        data(geo_hash_internal) = to_lower( geohash ).        data(hash_index) = 0.        do length times.          data(base32) = geo_hash_internal+hash_index(1).          data(decimals) = value #( mt_base32_code2[ base32 = base32 ]-decimals optional ).          data(bin5) = conv numc5( dec_to_bin( decimals ) ).          data: mix_bin       type string,              longitude_bin type string,              latitude_bin  type string.          mix_bin = mix_bin && bin5.          hash_index = hash_index + 1.        enddo.        data(bin_index) = 0.        do strlen( mix_bin ) times.          data(bin) = mix_bin+bin_index(1).          if bin_index mod 2 = 0.          longitude_bin = longitude_bin && bin.        else.          latitude_bin = latitude_bin && bin.        endif.          bin_index = bin_index + 1.        enddo.        data(longitude_left)  = c_longitude_min.      data(longitude_right) = c_longitude_max.      data(latitude_left)   = c_latitude_min.      data(latitude_right)  = c_latitude_max.          data(longitude_index) = 0.        do strlen( longitude_bin ) times.          data(bin_longitude) = longitude_bin+longitude_index(1).          get_tude(          exporting            i_left  = longitude_left            i_right = longitude_right            i_bin   = bin_longitude          importing            e_left  = longitude_left            e_right = longitude_right            e_tude  = longitude        ).          longitude_index = longitude_index + 1.        enddo.        data(latitude_index) = 0.        do strlen( latitude_bin ) times.          data(bin_latitude) = latitude_bin+latitude_index(1).          get_tude(          exporting            i_left  = latitude_left            i_right = latitude_right            i_bin   = bin_latitude          importing            e_left  = latitude_left            e_right = latitude_right            e_tude  = latitude        ).          latitude_index = latitude_index + 1.        enddo.      endmethod.        method dec_to_bin.        "ignore negative number      data(temp) = 0.      data(dec) = i_dec.        while dec > 0.        temp = dec mod 2.        dec  = dec / 2 - temp.        r_bin = r_bin && conv char1( temp ).      endwhile.        r_bin = reverse( r_bin ).      endmethod.        method encode.        if length < 1.        return.      endif.        if length > c_max_hash_length.        data(hash_length) = c_max_hash_length.      else.        hash_length = length.      endif.        data(loop_times) = hash_length * 5 / 2 + 1.        data: longitude_bin type string,            latitude_bin  type string,            mix_bin       type string.        data(longitude_left)  = c_longitude_min.      data(longitude_right) = c_longitude_max.      data(latitude_left)   = c_latitude_min.      data(latitude_right)  = c_latitude_max.        do loop_times times.          get_bin(          exporting            i_left  = longitude_left            i_right = longitude_right            i_tude  = longitude          importing            e_left  = longitude_left            e_right = longitude_right            e_bin  = data(longitude_bin_temp)        ).          get_bin(          exporting            i_left  = latitude_left            i_right = latitude_right            i_tude  = latitude          importing            e_left  = latitude_left            e_right = latitude_right            e_bin  = data(latitude_bin_temp)        ).          mix_bin = mix_bin && longitude_bin_temp && latitude_bin_temp.        enddo.        data(code_index) = 0.        do hash_length times.          data(offset) = code_index * 5 .        data(bin)    = mix_bin+offset(5).          r_geo_hash = r_geo_hash && value #(          mt_base32_code1[ decimals = bin_to_dec( i_bin = bin  ) ]-base32 optional ).          code_index = code_index + 1.        enddo.      endmethod.        method get_bin.        data(mid) = conv ty_tude( ( i_left + i_right ) / 2 ).        if i_tude <= mid.        e_bin   = c_zero.        e_left  = i_left.        e_right = mid.      else.        e_bin   = c_one.        e_left  = mid.        e_right = i_right.      endif.      endmethod.        method get_code_neighbor.        data(table_descr) = cast cl_abap_tabledescr( cl_abap_tabledescr=>describe_by_data( i_table ) ).        data(column_count) = lines(        cast cl_abap_structdescr( table_descr->get_table_line_type( ) )->components ).          data(col_index) = 1.        loop at i_table assigning field-symbol(<line>).          data(row_index) = sy-tabix.          col_index = 1.          while col_index <= column_count.            assign component col_index of structure <line> to field-symbol(<field>).          if sy-subrc = 0.            if <field> = i_member.              data(found) = abap_true.              exit.            endif.          endif.            col_index = col_index + 1.          endwhile.          if found = abap_true.          exit.        endif.        endloop.        if found = abap_false.        return.      endif.          types: begin of ty_direction,               row type i,               col type i,             end of ty_direction.        data: direction_index_table type standard table of ty_direction.        direction_index_table = value #(        ( row = -1 col =  -1  )        ( row = -1 col =   0  )        ( row = -1 col =  +1  )        ( row =  0 col =  -1  )        ( row =  0 col =  +1  )        ( row =  1 col =  -1  )        ( row =  1 col =   0  )        ( row =  1 col =  +1  )      ).        data(row_count) = lines( i_table ).        loop at direction_index_table assigning field-symbol(<direction_index>).          data(row_result) = get_index( index = row_index offset = <direction_index>-row max_index = row_count ).        data(col_result) = get_index( index = col_index offset = <direction_index>-col max_index = column_count ).          read table i_table assigning <line> index row_result.        if sy-subrc = 0.          assign component col_result of structure <line> to <field>.          if sy-subrc = 0.            r_table = value #( base r_table ( hash = <field> ) ).          endif.        endif.        endloop.      endmethod.        method get_index.        if abs( offset ) >= max_index.        return.      endif.        r_index = index + offset.        if r_index > max_index .        r_index = offset.      endif.        if r_index <= 0.        r_index = max_index + r_index.      endif.      endmethod.        method get_tude.        data(mid) = conv ty_tude( ( i_left + i_right ) / 2 ).        if i_bin = c_zero.        e_left  = i_left.        e_right = mid.        e_tude  = ( i_left + mid ) / 2.      else.        e_left  = mid.        e_right = i_right.        e_tude  = ( mid + i_right ) / 2.      endif.      endmethod.        method neighbors.        if geohash is initial.        return.      endif.        data(geohash_internal) = to_lower( geohash ).        data(length) = strlen( geohash_internal ).        data(offset) = length - 1.        data(suffix) = geohash_internal+offset(1).        if length mod 2 = 0.        data(code_table) = get_code_neighbor( i_table = mt_neighbors_even i_member = suffix ).      else.        code_table       = get_code_neighbor( i_table = mt_neighbors_odd  i_member = suffix ).      endif.        data(prefix) = geohash_internal(offset).        loop at code_table assigning field-symbol(<hash>).        neighbors = value #( base neighbors ( hash = prefix && <hash>-hash ) ).      endloop.      endmethod.        method validate.        valid = abap_false.        if geohash is initial .        return.      endif.        if strlen( geohash ) > c_max_hash_length.        return.      endif.        data(geohash_internal) = to_lower( geohash ).        data(geohash_index) = 0.        do strlen( geohash ) times.          data(hash) = geohash_internal+geohash_index(1).          if not line_exists( mt_base32_code2[ base32 = hash ] ).          return.        endif.          geohash_index = geohash_index + 1.        enddo.        valid = abap_true.      endmethod.  endclass.

 

使用

本节包含一些使用示例。

编码

以浙江省丽水中学的经纬度坐标 (28.4751600000, 119.9314500000) 为例,

 

 编码代码如下,

report ztest_qq1.    data(hash) = zcl_geohash=>encode(    i_latitude  = '28.4751600000'    i_longitude = '119.9314500000'  ).    cl_demo_output=>display( hash ).

 

可以得到结果wtj3cper。

 

 

 

默认的geohash长度是8位,也可以使用更长的编码提高精度,比如,

data(hash) = zcl_geohash=>encode(    i_latitude  = '28.4751600000'    i_longitude = '119.9314500000'    i_length    = 12  ).

 

可以得到wtj3cperv6d9。12是geohash-abap支持的最大长度。

解码

对上面得到的geohash编码结果wtj3cperv6d9进行解码,

zcl_geohash=>decode(    exporting      i_geo_hash = hash    importing      e_latitude  = data(latitude)      e_longitude = data(longitude)    ).    cl_demo_output=>display( latitude && ',' && longitude ).

 

得到的结果是 (28.475159956144, 119.931449834260) 可以看到是一个近似结果,和原值有微小的差距。

 

查询

将地点的geohash存储在数据库中之后,可以方便地用SQL中的like关键字,查找到附近的地点。

比如select * from table where geohash like ‘wtj3cperv%’等等…

相邻区域编码

为了准群找到最近的地址,需要找到一个geohash所代表的区域的周围8个区域。

为什么?

比如,下图中边缘附近的红点。

黄色的点要比黑色的点更加靠近红点,但是由于黑点跟红点的GeoHash前缀匹配数目更多,因此直接用like查询,会得到黑点。

(参考文章:https://blog.csdn.net/youhongaa/article/details/78816700

获取附近8个区域编码的方法:

data(neighbors) = zcl_geohash=>neighbors( 'wtj3cper' ).

 

参考:用打表的方式解决求Geohash当前区域周围8个区域编码