Skip to content

Commit

Permalink
Add Post: Mysql 공간 데이터 정리
Browse files Browse the repository at this point in the history
  • Loading branch information
Chaerim1001 committed Jan 8, 2024
1 parent 8092375 commit 5be934a
Showing 1 changed file with 172 additions and 0 deletions.
172 changes: 172 additions & 0 deletions _posts/2022-09-06-Mysql 공간 데이터 정리.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
title: Mysql 공간 데이터 정리
date: 2022-09-06
categories: [DB]

tags: [Mysql, TypeORM]
---

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

> 본 포스트는 아래의 환경을 기준으로 작성되었습니다. <br>
> [typeorm](https://www.npmjs.com/package/typeorm) 0.3.0 <br>
> [Mysql](https://www.npmjs.com/package/typeorm-transactional) 5.7
{: .prompt-info }

# 공간 데이터베이스란?
{: .mt-4 .mb-4 }

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

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

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

<img src="https://blog.kakaocdn.net/dn/0ExKk/btqDurscnn8/rK3KBj5ixQNkUdaK6QKit0/img.png">

| 타입 | 정의 | 예시 |
| :- | :- | :- |
| 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의 공간 함수
{: .mt-7 .mb-4 }
Mysql에서는 다양한 공간 함수를 제공하고 있습니다. 그 중에서 자주 사용되는 함수들은 아래의 표와 같습니다.


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


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

다양한 함수들이 많으니 공식 문서를 참고해서 원하는 기능의 함수를 잘 찾아서 사용하면 좋을 것 같습니다.
<a href="https://dev.mysql.com/doc/refman/5.7/en/spatial-function-reference.html">Mysql 공간 함수</a>

# Example
{: .mt-7 .mb-4 }
> 지금부터는 NestJS에서 TypeORM을 활용하여 공간 데이터를 사용하고 저장하는 방법에 대해 알아보곘습니다.
{: .prompt-tip }

## (1) Entity
`TypeORM``wkx` 라이브러리를 사용하여 정의한 Route 엔티티입니다.

```typescript
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;
}
```
여기서 사용한 `wkx``Well Known Text`의 약자로, 이해하기 쉬운 문자열로 데이터를 표현하는 방식이며 이를 이용해서 GIS(공간 정보 시스템)의 공간 데이터의 Row 단위를 나타낼 수 있습니다. `wkx`를 사용하면 mysql에서 지원하는 공간 데이터 타입들을 표현할 수 있기에 startPoint와 route 컬럼의 타입을 정의해주었습니다.

npm을 통해 설치해주어야 사용이 가능합니다. <a href="https://www.npmjs.com/package/wkx"> npm-wkx </a>

## (2) DTO
프론트에서 전달받는 위치 정보는 `latitude``longitude`로 이루어진 배열 데이터라고 가정하겠습니다. <br>
이는 `eachPoint` 타입을 정의해서 사용하였고 경로 데이터는 2개 이상의 Point가 필수적으로 필요하기 때문에 `@ArrayMinSize(2)`으로 설정해줍니다.

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

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

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

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

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

```typescript
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;
}
}
```

## (4) 결과 확인
### request-body
```javascript
{
"routeData": [{
"latitude": 0,
"longitude": 0
}, {
"latitude": 1,
"longitude": 1
}]
}
```

<img src="https://velog.velcdn.com/images/chaerim1001/post/d4d55b1f-2c85-4dcc-b473-2b3966c4d398/image.png">

DB에서 select문을 통해 결과를 확인하고 싶을 때에는 `st_astext` 함수를 사용하면 됩니다.

<img src="https://velog.velcdn.com/images/chaerim1001/post/b882cfe9-6e6d-4413-8a8e-9007a9e8ca31/image.png">

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

<br>

> 참고 <br>
> 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

0 comments on commit 5be934a

Please sign in to comment.