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

EPSG는 European Petroleum Survey Group의 약자로 전세계의 다양한 좌표계를 표준 코드화하여 최대한 많은 정보를 압축하여 제공하는 방식입니다. EPSG의 약자에 Survey, 즉 측량이 있다는 것은 GIS의 근본이 되는 좌표와 그 좌표가 측량으로 측정되고 존재한다는 의미로 측량의 중요성을 나타낸다고 할 수 있습니다.

이러한 EPSG에 대한 정보는 epsg.io라는 사이트를 통해 코드값으로 질의(Query) 함으로써 얻을 수 있습니다 예를 들어, 우리나라의 GRS80 타원체의 UTM-K 좌표계인 EPSG:5179에 대한 정보를 얻기 위해 아래와 같은 url을 호출함으로써, 그 결과를 JSON으로 얻을 수 있습니다.

https://epsg.io/?format=json&q=5179

결과는 아래와 같습니다.

{  
    "status":"ok",
    "number_result":1,
    "results":[  
       {  
          "code":"5179",
          "kind":"CRS-PROJCRS",
          "bbox":[  
             40.27,
             122.71,
             28.6,
             134.28
          ],
          "wkt":"PROJCS[\"Korea 2000 / Unified CS\",GEOGCS[\"Korea 2000\",DATUM[\"Geocentric_datum_of_Korea\",SPHEROID[\"GRS 1980\",6378137,298.257222101,AUTHORITY[\"EPSG\",\"7019\"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[\"EPSG\",\"6737\"]],PRIMEM[\"Greenwich\",0,AUTHORITY[\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY[\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4737\"]],PROJECTION[\"Transverse_Mercator\"],PARAMETER[\"latitude_of_origin\",38],PARAMETER[\"central_meridian\",127.5],PARAMETER[\"scale_factor\",0.9996],PARAMETER[\"false_easting\",1000000],PARAMETER[\"false_northing\",2000000],UNIT[\"metre\",1,AUTHORITY[\"EPSG\",\"9001\"]],AUTHORITY[\"EPSG\",\"5179\"]]",
          "unit":"metre",
          "proj4":"+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs",
          "name":"Korea 2000 / Unified CS",
          "area":"Republic of Korea (South Korea) - onshore and offshore.",
          "default_trans":15831,
          "trans":[  
             15831
          ],
          "accuracy":1.0
       }
    ]
 }

위의 내용중 상대적으로 의미가 있는 부분은 좌표계의 단위를 나타내는 unit과 해당 좌표계의 국제 명칭인 name과 좌표계가 허용하는 지역의 범위인 bbox와 좌표계 변환을 위한 proj4 문자열인 proj4입니다. 누락된 부분은 타원체간의 변환계수인데요. 위의 경우 proj4의 문자열 값 중 towgs84에 해당되는 부분입니다. 최근 타원체가 GRS80이나 이와 거의 동일한 WGS84가 주로 사용되고, Bessel1841과 같은 타원체는 거의 사용되지 않음으로써 타원체간 변환계수의 중요성은 시간이 갈수록 점점 사라질 것입니다. 그럼에도 아직 우리나라의 GIS 데이터는 Bessel1841 타원체를 근간으로 하는 좌표계가 적용되어 있는 경우가 많습니다. 대표적으로 지적도가 그렇습니다.

여튼, EPSG.io라는 사이트의 서비스를 이용하여 쉽게 좌표계의 제원 정보를 조회할 수 있음으로, 이를 활용하여 좌표계간의 다양성을 해결할 수 있습니다. 예를들어 OpenLayers에는 원하는 좌표계를 등록하여 서로 다른 좌표계간의 좌표 변환이 가능한데, 그 예가 아래와 같습니다.

import proj4 from 'proj4';
import {register} from 'ol/proj/proj4.js';
import {get as getProjection, getTransform} from 'ol/proj.js';

var newProjCode = 'EPSG:5179';

proj4.defs(newProjCode, proj4def);
register(proj4);

var newProj = getProjection(newProjCode);
var fromLonLat = getTransform('EPSG:4326', newProj);

위의 proj4def는 5179 코드로 하여 epsg.io의 서비스를 통해 얻은 proj4의 문자열 값입니다. 최종적으로 formLonLat라는 함수를 얻을 수 있는데, 이함수는 EPSG:4326 좌표계를 EPSG:5179 좌표계로 변환할 수 있는 함수입니다. 참고로 EPSG:4326은 WGS84 타원체의 경위도 좌표계입니다.

EPSG.jar를 이용한 좌표 변환

EPSG.jar는 (주)지오서비스에서 개발한 EPSG 코드 기반의 좌표변환을 위한 Java 라이브러리입니다. 안드로이드 클라이언트 GIS 엔진인 BlackPoint-Xr(현재 SmartPoint-Xr로 제품명이 변경됨)등과 같은 프로그램에서 이용되고 있습니다. 이 EPSG 라이브러를 이용한 좌표 변환에 대한 예를 기록해 둡니다.

먼저 필요한 jar는 EPSG.jar와 종속성을 갖는 ellip2ellipsoid.jar, javaproj-1.0.6-noawt.jar 입니다.

예제 코드는 다음과 같습니다.

package testEPSG;

import geoservice.ellip2ellipsoid.ConstantParameters10;
import geoservice.ellip2ellipsoid.IParameters;
import geoservice.ellip2ellipsoid.Values3;
import geoservice.epsg.EPSG;
import geoservice.epsg.EPSGFactory;

public class MainEntry {
	public static void main(String[] args) {
		EPSG fromEPSG = EPSGFactory.create(5174); 
		EPSG toEPSG = EPSGFactory.create(5179); 

		IParameters params = new ConstantParameters10();
		
		Values3 pt = new Values3(200000, 500000, 0);
		
		fromEPSG.to(pt, toEPSG, params);
		
		System.out.println(pt.toString());
	}
}

EPSG:5174 좌표계의 (200000, 500000)를 EPSG:5179로 변환하는 코드인데요. EPSG:5174는 Bessel 타원체를 사용하며, 일본의 기준점을 활용하면서 발생한 경도상의 오차인 10.405가 반영된 대한민국 중부원점 좌표계이고, EPSG:5179는 GRS80 타원체의 단일원점 좌표계인 UTM-K 좌표계입니다.

웹 크롤링

Python을 이용해 웹 페이지에서 원하는 정보를 추출해 내는 코드다. 아래는 네이버 뉴스의 오늘 날짜에 대한 토픽 제목과 해당 url을 뽑아내는 코드 예제.

import requests
from bs4 import BeautifulSoup

url = 'https://news.naver.com/main/ranking/popularDay.nhn?rankingType=popular_day&date=20191022'

r = requests.get(url)
html = r.content
soup = BeautifulSoup(html, 'html.parser')
titles_html = soup.select('.ranking_section > ol > li > dl > dt > a')

for i in range(len(titles_html)):
    print('#' + str(i+1), titles_html[i].text)
    print(titles_html[i]['href'])
    print()

결과는 대략 아래와 같다.

#1 러 군용기 6대의 무력 시위···한반도 3면 다 헤집고 다녔…
/main/ranking/read.nhn?mid=etc&sid1=111&rankingType=popular_day&oid=025&aid=0002946814&date=20191022&type=1&rankingSectionId=100&rankingSeq=1

#2 시정연설 마친 文, 이철희에 건넨 말 "섭섭한가 시원한가"
/main/ranking/read.nhn?mid=etc&sid1=111&rankingType=popular_day&oid=025&aid=0002946806&date=20191022&type=1&rankingSectionId=100&rankingSeq=2

.
.

#5 文대통령 발언에 뒤집어진 ‘정시 확대’…“조국 사태가 교육…
/main/ranking/read.nhn?mid=etc&sid1=111&rankingType=popular_day&oid=020&aid=0003248596&date=20191022&type=1&rankingSectionId=100&rankingSeq=5

C#의 Resource Pool (리소스 풀)

C# 언어를 활용하여 스레드를 통해 코드를 동시에 실행하고자 하는데, DB Connection과 같은 유한한 자원을 미리 생성해 놓고 활용하고자 합니다. 이때 사용할 리소스 풀로써 만든 클래스입니다.

class ResourcePool<T>
{
    private readonly ConcurrentBag<T> _items = new ConcurrentBag<T>();
    private ManualResetEvent _event = new ManualResetEvent(true);

    public ResourcePool(List<T> items) {
        var iter = items.GetEnumerator();

        while(iter.MoveNext())
        {
            _items.Add(iter.Current);
        }
    }

    public void Release(T item)
    {
        _items.Add(item);
        _event.Set();
    }

    public T Get()
    {
        T item;

        do
        {
            if (_items.IsEmpty)
            {
                _event.Reset();
                _event.WaitOne();
            }
        } while (!_items.TryTake(out item));
            
        return item;
    }
}

위의 ResourcePool은 generic 클래스로 어떤 객체 타입이든 리소스로써 담아두고 활용할 수 있습니다. 예를 들어, 아래와 같은 클래스로 말입니다.

class Resource
{
    public int v {
        get;
        set;
    }

    private int _useCount = 0;

    public Resource(int v)
    {
        this.v = v;
    }

        
    public void increaseUsingCount()
    {
        _useCount++;
    }

    public string toString()
    {
        return "V: " + v + " Count: " + _useCount;
    }
}

이 리소스 풀에 스레드에서 사용할 리소스를 담아두는 코드의 예는 아래와 같습니다.

List<Resource> items = new List<Resource>() { new Resource(0), new Resource(1), new Resource(2), new Resource(3) };
ResourcePool<Resource> objPool = new ResourcePool<Resource>(items);

실제 리소스 풀은 스레드를 통해 활용될 때에 그 의미가 있습니다. 예를 들어 앞서 작성한 코드들을 전제로 아래의 코드처럼 말입니다.

Parallel.For(1, 50, new ParallelOptions { MaxDegreeOfParallelism = 8 },
    (int i) => {
        Resource r = objPool.Get();

        r.increaseUsingCount();
        Console.WriteLine("[Thread-Index: " + i + "] " + r.toString());

        // Some Operation
        Thread.Sleep(new Random().Next(600, 3000));
        // .

        objPool.Release(r);
    });

위의 코드는 총 49개의 스레드를 만들고, MaxDegreeOfParallelism에서 지정한 값이 8만큼, 최대 8개의 스레드만을 동시에 실행하도록 하며, 람다 함수를 통해 실행할 스레드 코드를 지정하고 있습니다.