대한민국 EPSG 코드

EPSG 코드는 전세계 좌표계 정의에 대한 고유한 명칭입니다. EPSG 코드에 대한 상세 정의는 prj4와 wkt라는 문자열로 되어 있으며, proj4와 EPSG의 wkt는 좌표계의 다양한 제원값을 정해진 문자열로 구성되어 있습니다. EPSG.io 라는 사이트를 통해 각 EPSG 코드에 대한 proj4와 wkt 문자열을 파악할 수 있으며, 이에 대한 글은 아래의 글을 참고하시기 바랍니다.

EPSG.io를 통한 proj4 문자열 얻기

아울러 아래는 대한민국에서 자주 사용하는 EPSG 코드에 대한 proj4 문자열을 정리한 것입니다. 향후에도 지속적으로 내용을 추가/보완하도록 할 것입니다.

EPSG:4166, ESPG:4326

+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs 

WGS84 타원체의 경위도 좌표계입니다. 흔히 GPS 등의 기본 좌표계입니다.

EPSG:2097

+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=bessel +units=m +no_defs +towgs84=-115.80,474.99,674.11,1.16,-2.31,-1.63,6.43

Bessel 1841 타원체의 한국 중부원점 TM 직각 좌표계입니다.

EPSG:5174

+proj=tmerc +lat_0=38 +lon_0=127.0028902777778 +k=1 +x_0=200000 +y_0=500000 +ellps=bessel +units=m +no_defs +towgs84=-115.80,474.99,674.11,1.16,-2.31,-1.63,6.43

일본의 기준점으로 끌어다 사용했다는 (개인적으로 사실이라고 해도 믿고 싶지 않지만?) 좌표계로, 일본에 지진이 발생하여 해당 기준점이 틀어진 만큼 보정된 좌표계입니다. 타원체는 Bessel 1841이며 TM 직각좌표계입니다. 폐기해야할 좌표계입니다. 개인적으로는 Bessel 타원체를 사용하는 모든 좌표계는 폐기하고 범세계적으로 GRS80 타원체로 통일해 사용해야 한다고 생각합니다.

ESPG:5178

+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=bessel +units=m +no_defs +towgs84=-115.80,474.99,674.11,1.16,-2.31,-1.63,6.43

Bessel 1841 타원체의 UTM-K 직각 좌표계입니다.

EPSG:5179 – 네이버 지도에서 사용함

+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +units=m +no_defs

GRS80 타원체의 UTM-K 직각 좌표계입니다. 네이버(v3)와 도로명주소 DB에서 사용하는 좌표계입니다.

ESPG:5181 – 카카오맵에서 사용함

+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=GRS80 +units=m +no_defs

GRS80 타원체의 한국 중부원점이며 Y 축으로 500000미터만큼 이동시킨 좌표계입니다. 카카오 맵에서 사용하는 좌표계입니다.

EPSG:5186

+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs

GRS80 타원체의 한국 중부원점이며 Y 축으로 600000미터만큼 이동시킨 좌표계입니다. 국가에서 제공하는 DXF나 NGI 형식의 공간데이터가 이 좌표계를 이용합니다. 중부원점이외에도 서부, 동부, 동해원점이 있으며 각각 EPSG:5185, EPSG:5187, EPSG:5188입니다.

EPSG:3857, EPSG:900913, EPSG:102113 – 네이버(v5), 구글맵, VWorld지도에서 사용함

+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs

구글맵, VWorld 지도에서 사용하는 좌표계입니다. TM 방식의 좌표계가 아니므로 거리 측정에 사용할 수 없는 좌표계입니다.

아울러 아래의 글은 자바스크립트 기반의 좌표계 라이브러리에 대한 설명입니다.

Proj4js

끝으로 앞서 언급한 몇가지 EPSG 코드에 대한 .prj 파일을 공유합니다.


Javascript 기반의 압축 라이브러리, jszip

웹에서는 로컬에 저장된 여러 개의 파일을 사용하기 위해서는 사용자가 해당 파일들을 모두 마우스를 통한 직접적인 선택 행위가 적용되어야 보안상에 문제가 발생하지 않습니다. 이에 대한 대안은 여러개의 파일을 하나의 파일로 묶아 압축하고 압축된 파일 하나에만 이러한 사용자의 선택 행위가 적용되도록 하는 것입니다. 물론 네트워크를 통한 파일의 접근에는 이러한 문제가 발생하지 않습니다.

여튼, 여러 개의 파일이 하나로 묶인, 하나의 압축 파일로써 zip을 처리할 수 있는 자바스크립트 기반의 라이브러리가 몇가지 되는데.. 그중 제가 사용한 라이브러리를 소개합니다. 바로 jszip인데요. 아래의 해맑고 환하게 웃는 인상좋은 개발자의 github에 방문해 다운로드 받을 수 있습니다.

jszip 라이브러리 이외에도 이를 좀더 쉽게 사용하기 위해서 jszip-utils 라이브러리도 함께 받았는데, 위의 그림에서 이 2개의 라이브러리에 대한 다운로드는 파랑색 외곽선 박스로 언급해 두었습니다.

사용코드는 아래와 같습니다. 만약 자바스크립트의 promise라는 개념을 이해하고 있다면 매우 심플하고 직관적이라는 것을 알 수 있습니다. IO 처리이므로 당연이 비동기방식으로 처리됩니다. 과거 버전에서는 동기 방식도 지원했던거 같은데, 제거된 것 같습니다.





위의 코드에서는 압축 파일 안에 저장된 하나 하나의 파일에 순차적으로 접근해서 압축을 풀고, 풀어진 데이터를 ArrayBuffer 타입으로 전달받도록 21번 째 코드가 file.async(“arraybuffer”)라고 지정되어 있으나, arraybuffer 대신 string, blob로도 지정하여 각각 텍스트, BLOB 타입으로도 전달받을 수 있습니다.

현대의 웹은 무한한 접근성이라는 장점은 있으나 여전이 데스크탑 환경에 비해 처리 속도가 느리지만, 이런 문제점 조차도 웹어셈블리 등의 다양한 기술을 통해 극복해 나가고 있습니다. 향후 몇년안에 모든 프로그램은 서비스화될 것이라고 생각되고, 프로그램의 서비스화에는 JavaScript라는 핵심적인 언어가 중추적인 역할을 할 것입니다.

NexGen, 웹 GIS에서 로컬 데이터 파일 활용

웹기반의 GIS 시스템은 지도탐색, 주소검색, 길찾기 등과 같은 매우 편리한 기능을 제공하며 인터넷이 되는 환경이라면 쉽게 접근하여 사용할 수 있습니다. 그러나 보다 높은 수준의 GIS 기능을 원하는 전문 사용자나 특화된 업무를 GIS에 접목하고자 하는 사용자에게는 많이 부족합니다.

이러한 사용자들이 느끼는 웹 GIS 기능의 결핍은 웹 GIS 보다는 여전이 QGIS나 ArcGIS 등과 같은 Application 형태의 GIS 프로그램들을 선택하게 합니다. 하지만 웹 GIS는 Desktop GIS가 갖지 못하는 장점을 제공합니다. 설치 없이 URL을 통해 바로 접속해 사용할 수 있다는 편의성, 기본적으로 제공되는 완벽하고 아름다운 배경맵, 완벽한 주소검색과 길찾기 등의 기능은 사용자가 별도의 설정 없이 바로 사용할 수 있습니다.

반면 웹 GIS의 단점은 서버 측에서 제공하지 않은 데이터의 사용에 큰 제약이 존재합니다. 예를 들어서, 사용자가 자신만의 노하우가 반영된 의미있고 유용한 결과를 공간 데이터 파일로 제작했다면, 이 공간 데이터 파일을 웹 GIS의 배경지도에 중첩해 보고, 다양한 편의 기능에 적용하여 활용하고 싶은 경우가 있습니다. 이런 요구사항은 웹 GIS의 기능성에 비례하여 커집니다.

만약 사용자가 QGIS, ArcGIS 또는 공간 분석 툴을 이용해 생성한 공간 데이터 파일을 웹 GIS에서 완벽하게 활용할 수 있다면 앞서 언급한 사용자의 요구사항은 상당 부분 충족될 것입니다. 바로 웹 GIS 기반 시스템인 NexGen은 사용자가 자신의 로컬 PC에 저장된 공간 데이터 파일을 바로 활용할 수 있습니다.

아래는 사용자가 보유한 SHP 파일들을 NexGen의 배경지도 위에 중첩하고 그리기 심벌과 라벨 들을 설정하는 예를 시연하고 있습니다.

위의 예는 단순히 SHP 파일을 통한 지도 구성 정도의 데모이지만, SHP 파일을 활용할 수 있음으로 해서 SHP 파일이 제공하는 공간 데이터를 웹 GIS에서 제공하는 다른 기능과 융합하여 다양하게 활용될 수 있습니다.

덧붙여 아래의 글은 외부 파일로 CSV를 통한 통계지도를 저작하는 NexGen의 기능 소개입니다.

넥스젠(NexGen)의 통계지도 기능

AutoEncoder

AutoEncoder는 학습 데이터에 레이블 데이터를 별도로 구축할 필요가 없는, 주어진 데이터만으로 학습이 가능한 비지도 학습 신경망입니다. 엄밀히 말해 입력 데이터가 곧 레이블 데이터가 됩니다. AutoEncoder 신경망은 Encoder와 Decoder라는 2개의 신경망으로 구성됩니다. Encoder는 입력 데이터에서 중요한 정보만을 남기고 신경망의 입장에서 학습시에 중요하지 않다고 판단되는 정보는 제거함으로써 처음 입력 데이터의 크기보다 더 작은 크기의 데이터(z)를 생성해 주는 신경망입니다. Decoder는 Encoder가 생성한 z를 가지고 다시 처음의 입력 이미지로 복원하는 신경망입니다.

Encoder가 생성해 주는 보다 더 작은 크기의 데이터를 잠재 벡터(Latent Vector)이라고 하며, z라고 흔히 표기합니다. 이 z는 GAN의 Generator의 입력 데이터인 z와 그 의미가 동일선상에 놓여 있습니다. 잠재 벡터라고 하는 이유는 어떤 중요한 ‘의미’가 잠재되어 있는 데이터(벡터)이기 때문입니다. 결국 이 z에는 처음 입력 데이터에서 중요한 의미만을 남겨 놓은 것, 압축된 것이라고 할 수 있습니다. 또한 이 z에는 별로 중요하지 않거나 잡음같은 것들이 제거된 데이터라고 할 수 있습니다. 압축 차원에서 보자면 손실 압축입니다. 이러한 AutoEncoder 신경망의 용도는 차원감소, 중요한 의미 추출, 잠재벡터를 통한 복잡한 데이터의 공간상 시각화, 이미지 검색, Segmentation, Super Resolution 등 매우 다양합니다.

이러한 AutoEncoder를 이미지 압축과 복원의 관점에서 CNN 레이어를 사용해 구현함으로써 더욱 구체적인 내용을 정리해 보겠습니다. 딥러닝 라이브러리는 PyTorch를 사용하였습니다. TensorFlow 역시 신경망 구성 레이어는 동일하니 어렵지 않게 변환이 가능할 것입니다.

먼저 필요한 패키지들을 Import 해 둡니다.

import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import torchvision.datasets as dset
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import random
from torch.utils.data import DataLoader

압축과 복원 대상이 되는 이미지는 Fashion MNIST를 사용하겠습니다.

batch_size = 1024
root = './MNIST_Fashion'
transform = transforms.Compose([transforms.ToTensor()])
train_data = dset.FashionMNIST(root=root, train=True, transform=transform, download=True)
test_data = dset.FashionMNIST(root=root, train=False, transform=transform, download=True)

train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True, drop_last=True)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False, drop_last=True)

AutoEncoder의 신경망에 대한 클래스를 정의합니다.

z_size = 314

class AutoEncoder(nn.Module):
    def __init__(self):
        super(AutoEncoder, self).__init__()

        self.encoder = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=32, kernel_size=2, stride=2, bias=False),
            nn.LeakyReLU(),
            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=2, stride=2, bias=False),
            nn.LeakyReLU(),
            Reshape((-1,7*7*64)),
            nn.Linear(7*7*64, z_size),
            nn.LeakyReLU(),
        )

        self.decoder = nn.Sequential(
            nn.Linear(z_size, 7*7*64),
            nn.LeakyReLU(),
            Reshape((-1,64,7,7)),
            nn.ConvTranspose2d(in_channels=64, out_channels=32, kernel_size=2, stride=2, bias=False),
            nn.LeakyReLU(),
            nn.ConvTranspose2d(in_channels=32, out_channels=1, kernel_size=2, stride=2, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        out = self.encoder(x)
        out = self.decoder(out)
        return out

위의 코드가 기술하는 신경망의 구성 레이어들 사이로 전달되는 Tensor의 크기를 표기한 그림은 다음과 같습니다.

AutoEncoder 클래스는 encoder와 decoder로 레이어들을 분리해 구성하고 있는 것을 볼 수 있습니다. 이렇게 해두면 추후에 encoder 만을 이용해 잠재 벡터만을 쉽게 얻어낼 수 있습니다. 보시면 encoder와 decoder는 레이어의 구성이 성호 대칭성이 있는 것을 볼 수 있습니다. 또한 앞서 언급 했듯이 AutoEncoder는 입력 데이터가 레이블 데이터로 사용되므로 출력 데이터가 입력 데이터의 텐서 크기와 동일합니다. 중요한 점은 z의 크기인 z_size를 314로 해두었는데, 이는 원본 이미지의 크기 28×28인 784가 약 40%의 크기로 압축된다는 것입니다. 다시 상기하면 이 40% 크기로 압축된 z에는 원본 이미지의 중요한 특징값들이 잠재되어 있고 불필요한 것이라고 판단되는 것들은 제거되어 있다는 것입니다. 물론 이러한 판단은 신경망이 데이터를 통해 스스로 판단합니다.

추가적으로 위의 신경망 클래스의 구성 레이어 중 Reshape라는 Tensor의 크기를 변경해 주는 레이어의 코드는 다음과 같습니다.

class Reshape(nn.Module):
    def __init__(self, shape):
        super(Reshape, self).__init__()
        self.shape = shape

    def forward(self, x):
        return x.view(*self.shape)

다음은 학습 코드입니다.

num_epochs = 15

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = AutoEncoder().to(device)
loss_func = nn.MSELoss().to(device)
optimizer = optim.Adam(model.parameters())
scheduler = lr_scheduler.ReduceLROnPlateau(optimizer,threshold=0.1, patience=1, mode='min')    

for i in range(num_epochs):
    for _, [image, _] in enumerate(train_loader):
        x = image.to(device)
        y_= image.to(device)
        
        optimizer.zero_grad()
        output = model(x)
        loss = loss_func(output, y_)
        loss.backward()
        optimizer.step()
 
    scheduler.step(loss)      
    print('Epoch: {}, Loss: {}, LR: {}'.format(i, loss.item(), scheduler.optimizer.state_dict()['param_groups'][0]['lr']))

몇차례 실행을 해보니 15 Epoch 정도에서 손실값이 더 이상 감소하지 않는 것을 확인했고 Overfitting을 방지하기 위해 최종적으로 반복 학습수를 15로 지정했습니다. 물론 어떤 신경망은 학습시에 손실값이 한동안 일정값에서 정체 하다가 다시 감소하는 경우가 있으므로 지속적이고 세밀한 관찰이 필요합니다.

학습이 완료되었다면, 그 결과를 시각화합니다.

model.eval()

rows = 4
for c in range(rows):
    plt.subplot(rows, 2, c*2+1)
    rand_idx = random.randint(0, test_data.data.shape[0])
    plt.imshow(test_data.data[rand_idx].view(28,28), cmap='gray')
    plt.axis('off')

    plt.subplot(rows, 2, c*2+2)
    inp = transform(test_data.data[rand_idx].numpy().reshape(28,28)).reshape(1,1,28,28).to(device)
    img = model(inp)
    plt.imshow(img.view(28,28).detach().cpu().numpy(), cmap='gray')
    plt.axis('off')

    print(test_data.targets[rand_idx])

plt.show()

결과는 아래와 같습니다.

총 4줄의 이미지들이 표시되는데, 왼쪽은 입력 이미지이고 오른쪽은 AutoEncoder의 Decoder가 생성해낸 이미지입니다.