폴리라인 인코딩 및 디코딩 (Google Maps Platform) : 구글시트 기본함수로 구현

Polyline encoding & Polyline decoding Algorithms (Google Maps Platform) : Used Google Sheets built-in functions(no Google Apps Scrips)

디코딩 폴리라인 예제 화면


'구글 폴리라인 알고리즘'의 설명에 따라 인코딩 및 디코딩 함수를 구글 시트의 기본함수로 구현하였다.

인코딩 과정은 알고리즘 설명의 단계 1부터 단계 11까지를 크게 3개의 파트로 나눌 수 있다.
  1. 좌표를 32비트 부호 있는 이진 정수 값(32 bit signed binary integer value)으로 변환
    • 단계 1부터 단계 5까지
    • 음수 좌표: ABS(ROUND(음수 좌표 * POWER(10, 5), 0)) * 2 - 1
    • 양수 좌표: ABS(ROUND(양수 좌표 * POWER(10, 5), 0)) * 2

  2. 이진 값을 Base64 인코딩 체계를 사용하여 문자 코드(character codes)로 변환
    • 단계 6부터 단계 9까지
    • BITOR(), HEX2DEC() 함수 사용

  3. 문자 코드(character codes)를 ASCII 문자로 변환
    • 단계 10부터 단계 11까지
    • CHAR() 함수 사용
  • 폴리라인의 각 점(Point)의 위도와 경도는 이전 점(Point)의 위도와 경도와의 상대좌표를 사용 : to conserve space, points only include the offset from the previous point (except of course for the first point)

디코딩 과정은 인코딩 과정의 역순으로 진행했다.
  • 인코딩된 폴리라인(ASCII 문자열)에서 각 점(Point)의 위도와 경도를 구분하기 위한 단락은 CODE(ASCII 문자) - 63 < HEX2DEC("0x20") 를 기준하였다.

  • 알고리즘 설명의 단계 11부터 단계 6까지의 디코딩은 BASE(BITAND(CODE(ASCII 문자) - 63, HEX2DEC("0x20") - 1), 2, 5) 함수식을 적용하였다.

예제시트를 열려면 여기를 클릭하여 파일을 연다. '메뉴 > 파일 > 사본 만들기'를 클릭한다.(In the menu, click File > Make a copy)
예제시트에 액세스할 수 없는 경우 여기를 마우스 오른쪽 버튼으로 클릭하고 '시크릿 창에서 링크 열기' 선택.

  • Named Function : GMAPS_ENCODEPATH(absolute_coordinate_pairs, rewrapping)
=LET(reference, " 구글 폴리라인 알고리즘 : https://developers.google.com/maps/documentation/utilities/polylinealgorithm ",
     rules, " 입력된 절대좌표에서 누락(공백)된 좌표는 0으로 적용 ",

     desc_0, " 절대좌표 정리" &
             " 적정 위도(-90.00000 <= 범위 <= 90.00000)를 벗어난 절대좌표를 -90.00000 <= 범위 <= 90.00000 로 변환 (예: 91->89, 182->-2, 273->-87, 360->0, 364->4, 540->90, -91->-89, -182->2, -273->87, -360->0, -364->-4, -540->-90) "  &
             " 적정 경도(-180.00000 <= 범위 <= 180.00000)를 벗어난 절대좌표를 -180.00000 <= 범위 <= 180.00000 로 변환 (예: 181->-179, 360->0, 362->2, 540->180, -182->178, -360->0, -363->-3, -540->-180) ",
     AbsoluteCoordinatesRewrapping,
       LAMBDA(absolute_pairs,
              LET(reshaped, WRAPROWS(TOROW(absolute_pairs, 2), 2, 0),
                  filtered, FILTER(reshaped, ISNUMBER(CHOOSECOLS(reshaped, 1)), ISNUMBER(CHOOSECOLS(reshaped, 2)) ),
                  IF(rewrapping
                     , HSTACK(MAP(CHOOSECOLS(filtered, 1), LAMBDA(absolute, IFERROR(1/ISBETWEEN(absolute, -90,   90, TRUE, TRUE) * absolute, DEGREES(ASIN(SIN(RADIANS(absolute)))) ) )),
                              MAP(CHOOSECOLS(filtered, 2), LAMBDA(absolute, IFERROR(1/ISBETWEEN(absolute, -180, 180, TRUE, TRUE) * absolute, MOD(absolute + 180, 360) - 180 ) )) )
                     , filtered) 
                 )
             ),

     desc_1, " 절대좌표를 E5 포멧의 상대좌표로 변환 및 인코딩된 경도와 위도로 변환" &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 Latitude, Longitude에서 Change In Latitude, Change In Longitude까지 변환 및 알고리즘 단계 1 -> 2 -> 3 -> 4 -> 단계 5 까지 "  &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 Change In Latitude, Change In Longitude에서 Encoded Latitude, Encoded Longitude까지 변환 및 알고리즘 단계 6 -> 7 -> 8 -> 9 -> 10 -> 단계 11 까지 "  &
             " Tip. 입력된 절대좌표에서 누락(공백)된 좌표는 0으로 적용 ",
     AbsoluteCoordinatesToE5InRelativeCoordinates,
       LAMBDA(absolute_pairs,
              LET(e5_in_relative_coordinates, MAP(absolute_pairs,
                                                  ARRAY_CONSTRAIN(VSTACK(HSTACK(0, 0), absolute_pairs), ROWS(absolute_pairs), 2)
                                                  , LAMBDA(absolute, unshift_zero
                                                           , IF(EQ(SIGN(absolute - unshift_zero), -1)
                                                                , ABS(ROUND((absolute - unshift_zero) * POWER(10, 5), 0)) * 2 - 1
                                                                , ABS(ROUND((absolute - unshift_zero) * POWER(10, 5), 0)) * 2    ) )),
                  IF(EQ(COUNT(absolute_pairs), 1) , INDEX(e5_in_relative_coordinates, 1, 1)
                                                  , e5_in_relative_coordinates )
                 )
             ),

     desc_2, " 상대좌표를 인코딩된 경도와 위도로 변환 " &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 Change In Latitude, Change In Longitude에서 Encoded Latitude, Encoded Longitude까지 변환 "  &
             " 및 알고리즘 단계 6 -> 7 -> 8 -> 9 -> 10 -> 단계 11 까지 변환 ",
     E5InRelativeCoordinatesToEncodedLatitudeEncodedLongitude,
       LAMBDA(e5_in_relative_coordinates,
              MAP(e5_in_relative_coordinates, LAMBDA(e5_in_relative_coordinate, 
                  LET(nos_chunks, CEILING(LENB(BASE(e5_in_relative_coordinate, 2)), 5) / 5,
                      signed_xbit_binary, BASE(e5_in_relative_coordinate, 2, nos_chunks * 5),
                      IF(LT(nos_chunks, 2)
                         , CHAR(e5_in_relative_coordinate + 63)
                         , REDUCE(CHAR(DECIMAL(LEFTB(signed_xbit_binary, 5), 2) + 63),
                                  SEQUENCE(1, nos_chunks - 1, 5 + 1, 5)
                                  , LAMBDA(chunk_ascii, starting_at,
                                           TRIM(CHAR(BITOR(HEX2DEC("0x20"), DECIMAL(MIDB(signed_xbit_binary, starting_at, 5), 2)) + 63) & chunk_ascii) ))
                        )
                     )
                 ))
             ),

     desc_3, " 인코딩된 경도와 위도를 인코딩된 점(Encoded Point)로 변환 " &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 Encoded Latitude, Encoded Longitude에서 Encoded Point까지 변환 ",
     EncodedLatitudeEncodedLongitudeToEncodedPoint,
       LAMBDA(encoded_latitude_encoded_longitude_pairs,
              BYROW(encoded_latitude_encoded_longitude_pairs, LAMBDA(current_row, TEXTJOIN(IFERROR(,), TRUE, current_row) )) 
             ),

     desc_4, " 인코딩된 점(Encoded Point)들을 인코딩된 폴리라인(Encoded polyline)으로 변환 " &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 오른쪽 Encoded Point들을 최하단 Encoded polyline으로 변환 ",
     EncodedPolyline,
       LAMBDA(encoded_points,
              TEXTJOIN(IFERROR(,), TRUE, encoded_points) 
             ),

     EncodedPolyline(
       EncodedLatitudeEncodedLongitudeToEncodedPoint(
         E5InRelativeCoordinatesToEncodedLatitudeEncodedLongitude(
           AbsoluteCoordinatesToE5InRelativeCoordinates(
             AbsoluteCoordinatesRewrapping( absolute_coordinate_pairs )))))

    )

  • Named Function : GMAPS_DECODEPATH(encoded_polyline)
=LET(reference, " 구글 폴리라인 알고리즘 : https://developers.google.com/maps/documentation/utilities/polylinealgorithm ",
     rules, " 단일 좌표에서 누락된 경도좌표는 무시, 다중 좌표에서 누락된 마지막 경도좌표는 직전 경도좌표와 동일하게 적용 ",

     desc_1, " ASCII 문자열을 그룹화된 5비트 청크로 구분 " &
             " '구글 폴리라인 알고리즘'의 최하단 Encoded polyline를 Encoded Latitude 및 Encoded Longitude로 구분 "  &
             " 및 알고리즘 단계 11 -> 10 -> 9 -> 8 -> 7 -> 단계 6 까지 " &
             " ASCII는 위도1, 경도1, 위도2, 경도2 순서, 반환되는 그룹은 경도2, 위도2, 경도1, 위도1 순서 " &
             " Tip. 구분자 ;'에서 '는 SPLIT()함수에서 숫자형 문자열이 숫자로 변환되는것을 방지할 목적 : SPLIT()함수에서는 구분자 ; 사용할 것 ",
     EncodedPolylineToGrouped5BitChunks,
       LAMBDA(ascii_characters,
              REDUCE( , SEQUENCE(1, LEN(ascii_characters), LEN(ascii_characters), -1), LAMBDA(chunks, starting_at,
                     LET(code_value, CODE(MID(ascii_characters, starting_at, 1)),
                         IF(LT(code_value - 63, HEX2DEC("0x20"))
                            , chunks & ";'" & BASE(BITAND(code_value - 63, HEX2DEC("0x20") - 1), 2, 5)
                            , chunks &        BASE(BITAND(code_value - 63, HEX2DEC("0x20") - 1), 2, 5) )
                        )
                    ))
             ),

     desc_2, " 5비트 청크 그룹을 역순으로 변환 및 5비트 청크 그룹을 상대좌표로 변환 " &
             " '구글 폴리라인 알고리즘'의 최하단 Table의 Change In Latitude, Change In Longitude (단, 좌표값 포멧으로) "  &
             " 및 알고리즘 단계 5 -> 4 -> 3 -> 2 -> 단계 1 까지 " &
             " 입력 그룹은 경도2, 위도2, 경도1, 위도1 순서, 반환되는 그룹은 위도1, 경도1, 위도2, 경도2 순서 ",
     Place5BitChunkGroupsInReverseOrder_And_ToRelativeCoordinates,
       LAMBDA(grouped_5bit_chunks,
              LET(splitted, SPLIT(grouped_5bit_chunks, ";", FALSE, TRUE),
                  MAP(SEQUENCE(1, COUNTA(splitted), -1, -1), LAMBDA(column_index,
                      LAMBDA(converted_signed_value,
                             IF(ISEVEN(converted_signed_value),  converted_signed_value      / POWER(10, 5) /  2
                                                              , (converted_signed_value + 1) / POWER(10, 5) / -2 )
                            )( DECIMAL(CHOOSECOLS(splitted, column_index), 2) )
                     ))
                 )
             ),

     desc_3, " 상대좌표를 절대좌표로 변환하여 래핑 " &
             " 반환되는 배열의 열(Column) 순서는 위도(절대좌표), 경도(절대좌표) " &
             " Tip. 단일 좌표에서 누락된 경도좌표는 무시, 다중 좌표에서 누락된 마지막 경도좌표는 직전 경도좌표와 동일하게 적용 ",
     WrapsInAbsoluteCoordinates,
       LAMBDA(relative_coordinates_of_single_row,
              IF(EQ(COUNT(relative_coordinates_of_single_row), 1)
                 , relative_coordinates_of_single_row 
                 , BYCOL(WRAPROWS(relative_coordinates_of_single_row, 2, 0), LAMBDA(relative_coordinates,
                         SCAN(, relative_coordinates, LAMBDA(absolute, relative, absolute + relative ))
                        ))
                )
             ),

     WrapsInAbsoluteCoordinates(
       Place5BitChunkGroupsInReverseOrder_And_ToRelativeCoordinates(
         EncodedPolylineToGrouped5BitChunks( encoded_polyline )))

    )


Comments