Polyline encoding & Polyline decoding Algorithms (Google Maps Platform) : Used Google Sheets built-in functions(no Google Apps Scrips)
'구글 폴리라인 알고리즘'의 설명에 따라 인코딩 및 디코딩 함수를 구글 시트의 기본함수로 구현하였다.
인코딩 과정은 알고리즘 설명의 단계 1부터 단계 11까지를 크게 3개의 파트로 나눌 수 있다.
- 좌표를 32비트 부호 있는 이진 정수 값(32 bit signed binary integer value)으로 변환
- 단계 1부터 단계 5까지
- 음수 좌표: ABS(ROUND(음수 좌표 * POWER(10, 5), 0)) * 2 - 1
- 양수 좌표: ABS(ROUND(양수 좌표 * POWER(10, 5), 0)) * 2
- 이진 값을 Base64 인코딩 체계를 사용하여 문자 코드(character codes)로 변환
- 단계 6부터 단계 9까지
- BITOR(), HEX2DEC() 함수 사용
- 문자 코드(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)
예제시트에 액세스할 수 없는 경우 여기를 마우스 오른쪽 버튼으로 클릭하고 '시크릿 창에서 링크 열기' 선택.
- Built-in functions used : ABS(), ARRAY_CONSTRAIN(), ASIN(), BASE(), BITAND(), BITOR(), BYCOL(), BYROW(), CEILING(), CHAR(), CHOOSECOLS(), CODE(), COUNT(), COUNTA(), DECIMAL(), DEGREES(), EQ(), FILTER(), HEX2DEC(), HSTACK(), IF(), IFERROR(), INDEX(), ISBETWEEN(), ISEVEN(), ISNUMBER(), LAMBDA(), LEFTB(), LEN(), LENB(), LET(), LT(), MAP(), MID(), MIDB(), MOD(), POWER(), RADIANS(), REDUCE(), ROUND(), ROWS(), SCAN(), SEQUENCE(), SIGN(), SIN(), SPLIT(), TEXTJOIN(), TOROW(), TRIM(), VSTACK(), WRAPROWS()
- 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
Post a Comment