Mysql의 공간 데이터 활용하기 🗺

Cherry·2022년 9월 5일
2
post-thumbnail

NestJS를 사용하는 프로젝트를 진행하다가 사용자의 경로 데이터를 저장해야 하는 상황이 생겨 공간 데이터를 사용하게 되었다. 공간 데이터 활용을 위해 공부했던 내용을 정리하고자 한다 💻

📍 공간 데이터베이스란

공간 데이터베이스란 말 그대로 공간 정보를 저장할 수 있는 데이터베이스를 뜻한다.
좀 더 명확한 개념을 이야기해보자면 공간에 존재하는 점, 선, 폴리곤등을 포함하는 객체의 데이터를 저장하고, 검색하는데 최적화된 데이터베이스라고 정의할 수 있다.

공간 데이터베이스는 단순히 공간 데이터를 저장해줄 뿐 아니라 공간 데이터를 활용한 공간 함수을 함께 제공한다.

🛣 Mysql의 공간 데이터 타입 (Spatial Data Type)

Mysql에서는 공간 데이터를 저장할 수 있도록 타입을 제공하고 있다.

타입정의예시
Point좌표 공간의 한 지점POINT(10 10)
LineString다수의 Point를 연결해주는 선분LINESTRING(10 10, 20 20, 30 30)
Polygon다수의 선분들이 연결되어 닫혀있는 상태POLYGON((10 10, 10 20, 20 10, 10 10))
Multi-Point다수의 Point 집합MULTIPOINT(10 10, 20 20)
Multi-LineString다수의 LineString 집합MULTILINESTRING((10 10, 20 20), (15 15, 25 25))
Multi-Polygon다수의 Polygon 집합MULTIPOLYGON(((10 10, 10 20, 20 10, 10 10)), ((40 40, 30 30, 40 40)))
GeomCollection모든 공간 데이터들의 집합GEOMETRYCOLLECTION(POINT(10 10), LINESTRING(20 20, 30 30))


📊 Mysql의 공간 함수

Mysql에서는 다양한 공간 함수를 제공하고 있다. 그 중에서 자주 사용되는 함수들은 아래의 표와 같다.

공간 연산 함수기능
ST_Intersection (g1 Geometry, g2 Geometry) : Geometryg1과 g2의 교집합인 공간 객체를 반환한다
ST_Buffer (g1 Geometry, d Double ) : Geometryg1에서 d 거리만큼 확장된 공간 객체를 반환한다
ST_StartPoint (l1 LineString) : Pointl1의 첫 번째 Point를 반환한다
ST_EndPoint (l1 LineString) : Pointl1의 마지막 Point를 반환한다
ST_PointN (l1 LineString) : Pointl1의 n번째 Point를 반환한다

공간 관계 함수기능
ST_Equals (g1 Geometry, g2 Geometry): Booleang1과 g2가 동일하면 True, 다르면 False를 반환한다
ST_Intersects (g1 Geometry, g2 Geometry): Booleang1과 g2의 교집합이 존재하면 True, 존재하지 않으면 False를 반환한다
ST_Contains (g1 Geometry, g2 Geometry): Booleang2가 g1 영역 안에 포함되는 경우 True, 그렇지 않으면 False를 반환한다
ST_Distance (g1 Geometry, g2 Geometry): Doubleg1과 g2간의 거리를 반환한다

다양한 함수들이 많으니 공식 문서를 참고해서 원하는 기능의 함수를 잘 찾아서 사용하면 좋을 것 같다.
Mysql 공간 함수

💻 NestJS & TypeORM & Mysql

NestJS에서는 어떻게 공간 데이터를 사용하고 저장할 수 있는지 경로 데이터를 저장하는 예시를 통해 알아보자.

NestJS version 9.0.0
TypeORM version 8.0.2
Mysql version 5.7

entity

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Point, LineString } from 'wkx';

@Entity()
export class Route {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ type: 'point' })
  startPoint: Point;

  @Column({
    type: 'linestring',
  })
  route: LineString;
}

TypeORMwkx 라이브러리를 사용하여 정의한 Route 엔티티이다.

여기서 사용한 wkxWell Known Text의 약자로, 이해하기 쉬운 문자열로 데이터를 표현하는 방식인데 GIS(공간 정보 시스템)의 공간 데이터의 Row 단위를 나타낼 수 있게 해준다.wkx를 사용하면 mysql에서 지원하는 공간 데이터 타입들을 표현할 수 있기에 startPoint와 route 컬럼의 타입을 정의해주었다.
(npm을 통해 설치해주어야 사용이 가능하다 -> npm-wkx )

dto

import { IsArray, ArrayMinSize } from 'class-validator';

type eachPoint = {
  latitude: number;
  longitude: number;
};

export class CreateRouteDto {
  @IsArray()
  @ArrayMinSize(2)
  readonly routeData: eachPoint[];
}

프론트에서 전달받는 위치 정보는 latitudelongitude로 이루어진 배열 데이터이기 때문에 eachPoint 타입을 정의해서 사용하였다.
경로 데이터는 2개 이상의 Point가 필수적으로 필요하기 때문에 @ArrayMinSize(2)으로 설정해주었다.

create

 async create(createRouteDto: CreateRouteDto) {
    const { routeData } = createRouteDto;
   
    const startPoint = `${routeData[0].latitude} ${routeData[0].longitude}`;

    const route = routeData.map((v) => {
      return `${v.latitude} ${v.longitude}`;
    });

    const linestring = route.join(',');

    try {
      await this.routeRepository
        .createQueryBuilder()
        .insert()
        .into(Route)
        .values({
          startPoint: () => `ST_GeomFromText('POINT(${startPoint})')`,
          route: () => `ST_GeomFromText('LINESTRING(${linestring})')`,
        })
        .execute();
    } catch (err) {
      throw err;
    }
  }

데이터를 DB에 저장하기 전에 Point 타입과 LineString 타입 데이터를 저장할 때에는 위의 표에 있는 예시와 같이 입력해줘야 하기 때문에 그에 맞게 데이터 형식을 바꿔주는 작업이 필요하다.

데이터 저장은 TypeORM의 queryBuilder를 사용하여 세부적인 쿼리문을 작성할 수 있었다.
ST_GeomFromText 함수를 사용해서 문자열 데이터를 공간 데이터 타입으로 변환하여 저장하였다.

결과

request-body

{
	"routeData": [
		{
			"latitude": 0,
			"longitude": 0
		},
		{
			"latitude": 1,
			"longitude": 1
		}
	]
}

DB에서 select문을 통해 결과를 확인하고 싶을 때에는 st_astext 함수를 사용할 수 있다!!

공간 함수도 잘 동작하는 것을 확인할 수 있다!

참고
https://orkhan.gitbook.io/typeorm/docs/entities#spatial-columns
https://dev.mysql.com/doc/refman/5.7/en/spatial-types.html
https://youngwoon.tistory.com/3
https://sparkdia.tistory.com/24

profile
호기심 많은 백엔드 개발자입니다 😝

0개의 댓글