[ 공모전 ] 지도 페이지 : GRS80TM(EPSG:2097) To WGS84 - 좌표변환이 사실 아닌 이유

최문길·2024년 7월 12일
0

공모전

목록 보기
22/46
post-thumbnail

먼저 본론부터 이야기 하자면,
EPSG:2097(서울시에서 제공한 좌표데이터 형식)을 WGS84변환이 안된다.
왠만한 블로그에서는 EPSG:2097을 WGS84로 바꾸면 된다라고 작성되어있지만, 직접 해보니 아니었다.
왜 아닌지에 대한 포스팅이다.

kakao map에서 기본적으로 위치 좌표계로 WGS84를 사용한다.
서울시 동물관련 데이터 좌표는 EPSG:2097을 사용하는데

  • kakao map에서 지원하는 다른 좌표계가 있는지,
  • 좌표를 어떤 방법으로 변환 할지

위의 2가지를 다뤄보겠다.

kakao.maps.services.Geocoder()

카카오에서는 주소-좌표계 변환을 지원한다.

var geocoder = new kakao.maps.services.Geocoder()
geocoder.transfer() // 좌표를 특정 좌표로 변환하는 메소드
geocoder.addressSearch() // 주소 정보에 해당하는 좌표값
geocoder.coord2Address() // 좌표 값에 해당하는 구 주소와 도로명 주소 정보를 요청
geocoder.coord2RegionCode() //좌표 값에 해당하는 행정동, 법정동 정보

다양한 주소-좌표계 메소드가 Geocoder class 안에 내장 되어있는데,
중요한 건 내장 API를 통해 Rest API로 호출 받는 다는 것이다.


geocoder.transfer 메소드

var geocoder = new kakao.maps.services.Geocoder()
geocoder.transfer() // 좌표를 특정 좌표로 변환하는 메소드

geocoder.transfer()를 사용하면
입력받을 수 있는 좌표와 출력하는 좌표계는 제한 되어있는데

export enum Coords {
  WGS84 = "WGS84",
  WCONGNAMUL = "WCONGNAMUL",
  CONGNAMUL = "CONGNAMUL",
  WTM = "WTM",
  TM = "TM",
}
//...
transCoord(
  //...
  input_coord: Coords
  output_coord: Coords
  )

transCoord의 input, output의 타입들을 보면 Coords 타입만을 반환 한다.
내가 원하는 좌표계인 GRS80TM은 없다...

geocoder.addressSearch 메소드

addressSearch 메소드는 특정 주소를 입력하면 결과 값이 주어지는데, 아래의 사진과 같다.

x: string; // X 좌표값, 경위도인 경우 경도(lng)
y: string // Y 좌표값, 경위도인 경우 위도(lat)

x,y좌표가 무슨 좌표계를 반환하는지 궁금할 수 있다.
위의 궁금증은 예시 코드로 알 수 있는데

geocoder.addressSearch('제주특별자치도 제주시 첨단로 242', function(result, status) {
    // 정상적으로 검색이 완료됐으면 
     if (status === kakao.maps.services.Status.OK) {

        var coords = new kakao.maps.LatLng(result[0].y, result[0].x);

        // 결과값으로 받은 위치를 마커로 표시합니다
        var marker = new kakao.maps.Marker({
            map: map,
            position: coords
        });
	//...생략

위의 코드는 kaka map api에서 제공하는 주소로 장소표시하기 예제를 가져왔다.
coords는 카카오맵과 호환되는 좌표계로 나타냄을 간단히 유추 할 수 있게 된다.


Geocoder.addressSearch로 좌표 변환

transfer 메소드는 서울시 데이터 좌표계와 호환 되지 않으므로 사용 할수 있는 메소드로는 addressSearch가 있다.

서울시 데이터안에는 도로명 주소와, 지번 주소도 포함되어있는 데이터를 보내므로, addressSearch를 사용하면 될것 같다.

데이터 한개 변환

const getAnimalData = async () => {
  const { data } = await getAnimalHospitalData('LOCALDATA_020301_DB/1/10/01')
  return data
}
//...생략
 const [db, setDB] = useState<string | null>(null)
 const { addressSearch } = new kakao.maps.services.Geocoder()
 const { data } = useQuery({
   queryKey: ['animal_hospital'],
   queryFn: getAnimalData,
   enabled: !!db, // 도봉구 클릭하면 useQuery실행
   select(data) {
     return data.LOCALDATA_020301_DB.row
   },
 })
  const address2Result = () => {
    if (!kakaoMap) return
    addressSearch(data[0].RDNWHLADDR, function (result, status) {
      // 정상적으로 검색이 완료됐으면
      if (status === kakao.maps.services.Status.OK) {
        const coords = new kakao.maps.LatLng(result[0].y, result[0].x)

        // 결과값으로 받은 위치를 마커로 표시합니다
        const marker = new kakao.maps.Marker({
          map: kakaoMap,
          position: coords,
        })

        // 지도의 중심을 결과값으로 받은 위치로 이동시킵니다
        kakaoMap.setCenter(coords)
      }
    })
  }
  useEffect(() => {
    if (!data) return
    address2Result()
  }, [data, db])

return (
 <div style={CONTAINER_STYLE}>
      <span // 도봉구를 클릭하면 useQuery 실행 
        onClick={() => setDB('DB')}
        style={...}
      >
        도봉구
      </span>
      <Map center={MAP_CENTER} style={MAP_STYLE} onCreate={handleKakaoMap}></Map>
    </div>

우선 샘플로 10개의 데이터 중 하나만 변환 하여 마커표시가 되는 지 확인 했다.



네트워크 통신이 생기네 ?

좌표 변환은 잘 되지만...
위에서 써놓은 것처럼 내장 API 함수이므로 네트워크 통신이 이루어지는 것을 확인 하였다.

하나의 좌표 변환당 하나의 네트워크 통신이 이루워진다.
1000개의 데이터의 좌표를 변환 하면 네트워크 통신은 1000개가 된다는 것을 의미한다.

Proj4 라이브러리

한개의 주소 데이터당, 하나의 데이터 통신이 이루워지는데,

proj4 라이브러리는 좌표계 변환을 해주는 라이브러리이다.

proj4 라이브러리 사용이유

client 단에서 모든 API를 통해 데이터를 받아오는 것은 무리라 판단해서이다.

  • 카카오 맵 API
  • 동물병원 API
  • 동물약국 API
  • 카카오 내장 API
  1. 총 4개의 API가 하나의 페이지에서 이루워지고 있는데, client단에서 전부 일어나는 이벤트들이므로, 모든 API에 따른 변수처리를 client단에서 커버하는 것은 사실 좋은 것은 아니라 생각했다. 로직의 복잡도, 등..

  2. 하나의 api라도 줄여서 호출시간과 변환 가정으로 인한 rendering을 줄이고자 proj4라이브러리를 사용하는 것이 더 낫겠다 판단 하였다.

  3. 각 500개의 데이터를 불러온다 생각했을 때 1000개의 데이터를 불러오고, 각 1000개의 데이터 마다 카카오 RestAPI를 통해 주소-> 좌표 변환을 하고 그 이후에 Rendering 되는 것이기에 Rendering되는 속도가 느리다.

따라서
proj4라는 라이브러리를 이용해서 GRS80TM-> WGS84로 바꾸는 것으로 판단 하였다.


Proj4를 사용하여 좌표 변환을 해보자

//...생략
import proj4 from 'proj4/'

// Korea 2000 좌표 시스템 정의 (EPSG:2097)
proj4.defs('EPSG:2097', '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=GRS80 +units=m +no_defs')

// WGS84 좌표 시스템 정의 - 카카오맵에서 사용하는 좌표계
proj4.defs('EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs')

// EPSG:2097 -> WGS84
const convertGRStoWGS84 = (x: string, y: string) => {
  const transformedCoords = proj4('EPSG:2097', 'EPSG:4326', [Number(x), Number(y)])
  const wgs84Coords = { lat: transformedCoords[1], lng: transformedCoords[0] }
  return wgs84Coords
}

// 비동기 호출
const getAnimalData = async () => {
  const { data } = await getAnimalHospitalData('LOCALDATA_020301_DB/1/10/01')
  return data
}


const KakaoPro4 = ({ query } : { query:string }) => {
 // ... 생략
const { data } = useQuery({
   queryKey: ['animal_hospital'],
   queryFn: getAnimalData,
   enabled: !!db, // 도봉구 클릭하면 useQuery실행
   select(data) {
     return data.LOCALDATA_020301_DB.row
   },
 })
 const proj42Result = () => {
    if (!kakaoMap) return
    if (!data) return
    const bounds = new kakao.maps.LatLngBounds()
    data.forEach((item: any, index: number) => {
      if (!item.X || !item.Y) return
      const wgs84Coords = convertGRStoWGS84(item.X, item.Y)
      const coords = new kakao.maps.LatLng(wgs84Coords.lat, wgs84Coords.lng)

      const marker = new kakao.maps.Marker({
        map: kakaoMap,
        position: coords,
      })
      // 경계 영역을 확장합니다
      bounds.extend(coords)

      // 모든 주소에 대해 검색이 완료된 후에 지도의 경계를 설정합니다
      if (index === data.length - 1) {
        kakaoMap.setBounds(bounds)
      }
    })
  }
 useEffect(() => {
   if (!data) return
   proj42Result()
 }, [db, data])
 
return (
    <div style={CONTAINER_STYLE}>
      <span
        onClick={() => setDB('DB')}
        style={{ width: '120px', backgroundColor: 'wheat', color: 'black', height: '80px', textAlign: 'center' }}
        >
        도봉구
      </span>
      <Map center={MAP_CENTER} style={MAP_STYLE} onCreate={handleKakaoMap}></Map>
    </div>
  )
} 

위의 코드 처럼 클릭시 useQuery 를 호출하여 비동기 통신과 리렌더링을 진행 하였다.

결과는

좌표변환이 실패

위치가 다르다. 그것도 오차가 조금 있네 정도가 아니라 아이에 다르다 . !!

사진에서 보면 기대되는 위치와 정 반대편에서 아이에 틀어지는 것을 확인 할 수 가 있다.


안되는 이유를 실제로 확인해 보았다.
사실 서울시에서 말하는 중부원점이 아니라 **** 이었다. 이 다음 포스팅에서 직접 눈으로 확인해보자.

0개의 댓글

관련 채용 정보