웹에서 SHP 파일 생성하기

SHP 파일은 최소 3가지 파일로 구성됩니다. 좌표 데이터가 저장된 .SHP, 이 좌표 데이터로 구성된 도형에 대한 인덱스가 저장된 .SHX, 속성 데이터가 저장된 .DBF 파일입니다.

웹 GIS에서 사용자가 공간 데이터를 활용하여 또 다른 의미의 공간 데이터를 생성해 낼 수 있을 것이고, 이 새로운 공간 데이터를 SHSP 파일 형태로 저장할 수 있다면 자신의 PC에 보관하거나, 다른 사용자와 파일 수준에서 공유할 수 있을 것입니다.

FingerEyes-Xr은 NexGen 솔루션 개발에 사용된 웹 GIS 클라이언트 라이브러리입니다. 이 FingerEyes-Xr에는 SHP 파일을 생성할 수 있는 기능을 제공하는데, 이에 대한 API를 정리해 둡니다.

먼저 생성하고자 하는 도형의 종류가 포인트인지, 폴리라인인지, 폴리곤인지를 지정하는 코드가 필요합니다. 여기서는 폴리곤입니다.

let shapeType = Xr.data.ShapeType.POLYGON;

속성 데이터의 구조를 정의하기 위해 아래의 코드가 필요합니다.

let fieldSet = new Xr.data.FieldSet();

fieldSet.add(new Xr.data.Field('field1', Xr.data.FieldType.STRING, 20));
fieldSet.add(new Xr.data.Field('field2', Xr.data.FieldType.INTEGER, 7));
fieldSet.add(new Xr.data.Field('field3', Xr.data.FieldType.FLOAT, 6, 2));

총 3개의 필드를 정의했으며 각각 문자열, 정수형, 실수형입니다. 문자열의 최대 길이는 20이며, 정수형의 최대 길이는 7이고, 실수형의 최대 길이는 6이면서 소수점 최대 길이는 2입니다.

이제 SHP 생성을 위한 팩토리를 정의합니다. 앞서 정의두었던 도형의 종류와 속성의 구조를 지정합니다.

let cntFields = fieldSet.count();
let factory = new Xr.export.ESRISHPFileFactory(fieldSet, shapeType);

이제 파일에 저장할 도형 좌표와 속성을 생성하고 팩토리에 추가합니다. 총 2개를 추가합니다.

let shape, attr;

shape = new Xr.data.PolygonShapeData([
    [
        new Xr.PointD(150267, 246895), new Xr.PointD(150367, 246895), new Xr.PointD(150367, 246995)
    ],
    [
        new Xr.PointD(150467, 247095), new Xr.PointD(150367, 247095), new Xr.PointD(150367, 247195)
    ]
]);

attr = new Xr.data.AttributeRow(-1, cntFields); // -1은 의미없음
attr.setValue(0, 'A가나다B');
attr.setValue(1, 100);
attr.setValue(2, 100.12);
let row1 = new Xr.export.RowSHP(shape, attr);

shape = new Xr.data.PolygonShapeData([
    [
        new Xr.PointD(150422, 246805), new Xr.PointD(150522, 246805), new Xr.PointD(150522, 246705)
    ]
]);

attr = new Xr.data.AttributeRow(-1, cntFields); // -1은 의미없음
attr.setValue(0, 'Hello');
attr.setValue(1, 200);
attr.setValue(2, 200.12);
let row2 = new Xr.export.RowSHP(shape, attr);

factory.addRow(row1);
factory.addRow(row2);

최종적으로 SHP 파일에 저장될 바이너리 데이터는 다음 코드를 통해 얻을 수 있습니다.

let shpObj = factory.export();

위의 shpObj 객체에는 shp, shx, dbf라는 속성이 존재하며 각각 앞서 언급한 .SHP, .SHX, .DBF 파일을 구성하는 바이너리 데이터가 ArrayBuffer의 배열로 담겨 있습니다. 실제 파일로의 저장은 아래의 코드를 통해 가능합니다. 속성 데이터에 대한 문자 인코딩은 UTF-8입니다.

if (isIE()) {
    saveToFile_IE('a.shp', shpObj.shp);
    saveToFile_IE('a.shx', shpObj.shx);
    saveToFile_IE('a.dbf', shpObj.dbf);
} else {
    saveToFile_Chrome('a.shp', shpObj.shp);
    saveToFile_Chrome('a.shx', shpObj.shx);
    saveToFile_Chrome('a.dbf', shpObj.dbf);
}

위의 코드에서 언급된 isIE, saveToFile_IE, saveToFile_Chrome 함수는 다음 글을 통해 상세히 파악할 수 있습니다. 참고로 코드 중 기존의 type: ‘text/plain’을 type: ‘application/zip’으로 변경했습니다.

웹에서 Javascript 만으로 텍스트 파일 생성

대한민국 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 파일을 공유합니다.


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)의 통계지도 기능

Proj4js

이 글은 proj4js.org 사이트에서 제공되는 내용을 파악하기 위해 정리한 포스트이며, 좀 더 상세한 내용을 추가적으로 담고자 노력하였습니다.

Proj4js는 좌표계 간의 상호 변환하기 위한 자바스크립트 라이브러리이며 서로 다른 타원체 간의 Datum 변환 기능을 포함하고 있습니다. 이 라이브러리는 원래 C언어로 개발된 PROJ.4와 MetaCRS 그룹의 프로젝트 중의 하나인 GCTCP C를 JavaScript로 포팅한 것입니다.

설치

개발자의 개발환경에 따라 다르지만, 아래의 4가지 방식 중 한가지 방식으로 설치가 가능합니다.

npm install proj4
bower install proj4
jam install proj4
component install proj4js/proj4js

또는 최신 배포의 dist/ 폴더에서 proj4.js 파일을 직접 사용할 수 있습니다. 아무것도 다운로드 받고 싶지 않다면 CDN을 통한 URL로 라이브러리를 사용해도 됩니다.

사용 방법

기본적인 사용 구문은 다음과 같습니다.

proj4(fromProjection[, toProjection, coordinates])

인자 fromProjection와 toProjection는 proj이거나 WTK 문자열일 수 있습니다. coordinates 인자는 {x:x,y:y}와 같은 객체 형태이거나 [x,y]와 같은 배열 형태일 수 있습니다.

fromProjection, toProjection, coordinates 인자가 모두 지정되면, 지정된 좌표가 fromProjection 좌표 체계에서 toProjection 좌표 체계로 변환된 결과를 반환합니다. 변환된 결과는 지정된 좌표 인자의 형태와 같습니다.

var firstProjection = 'PROJCS["NAD83 / Massachusetts Mainland",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",42.68333333333333],PARAMETER["standard_parallel_2",41.71666666666667],PARAMETER["latitude_of_origin",41],PARAMETER["central_meridian",-71.5],PARAMETER["false_easting",200000],PARAMETER["false_northing",750000],AUTHORITY["EPSG","26986"],AXIS["X",EAST],AXIS["Y",NORTH]]';

var secondProjection = "+proj=gnom +lat_0=90 +lon_0=0 +x_0=6300000 +y_0=6300000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs";

var result = proj4(firstProjection, secondProjection, [2, 5]);
// [-2690666.2977344505, 3662659.885459918]

만약 1개의 projection 만을 사용한다면 해당 인자는 fromProjection을 의미하며, firstProjection은 WGS84 경위도 좌표계가 됩니다.

var firstProjection = 'PROJCS["NAD83 / Massachusetts Mainland",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",42.68333333333333],PARAMETER["standard_parallel_2",41.71666666666667],PARAMETER["latitude_of_origin",41],PARAMETER["central_meridian",-71.5],PARAMETER["false_easting",200000],PARAMETER["false_northing",750000],AUTHORITY["EPSG","26986"],AXIS["X",EAST],AXIS["Y",NORTH]]';

var result = proj4(firstProjection, [-71, 41]);
// [242075.00535055372, 750123.32090043]

또한 coordinates 인자 없이 projection 인자만을 사용한다면, forward와 inverse 매서드를 갖는 객체가 반환되는데, forward는 fromProjection 좌표계에서 toProjection로의 좌표 변환을, inverse는 toProjection 좌표계에서 fromProjection로의 좌표 변환을 수행하는 매서드입니다.

var firstProjection = 'PROJCS["NAD83 / Massachusetts Mainland",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",42.68333333333333],PARAMETER["standard_parallel_2",41.71666666666667],PARAMETER["latitude_of_origin",41],PARAMETER["central_meridian",-71.5],PARAMETER["false_easting",200000],PARAMETER["false_northing",750000],AUTHORITY["EPSG","26986"],AXIS["X",EAST],AXIS["Y",NORTH]]';

var secondProjection = "+proj=gnom +lat_0=90 +lon_0=0 +x_0=6300000 +y_0=6300000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs";

var resultA = proj4(firstProjection, secondProjection).forward([2, 5]);
// [-2690666.2977344505, 3662659.885459918]

var resultB = proj4(secondProjection,firstProjection).inverse([2,5]);
// [-2690666.2977344505, 3662659.885459918]

projection 인자가 하나만 지정되면, 지정된 인자는 toProjection에 해당되며 fromProjection은 WGS84 경위도 좌표계가 됩니다.

이름을 가지는 투영변환

만약 문자열로 투영변환에 이름을 부여하고, 이 이름으로 좌표 변환을 수행하고자 한다면 proj4.defs 매서드를 사용할 수 있습니다.

proj4.defs('WGS84', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees");

또는 아래처럼 배열로 여러개의 투영변환을 정의할 수 있습니다.

proj4.defs([
  [
    'EPSG:4326',
    'PROJCS["NAD83 / Massachusetts Mainland",GEOGCS["NAD83",DATUM["North_American_Datum_1983",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],AUTHORITY["EPSG","6269"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4269"]],UNIT["metre",1,AUTHORITY["EPSG","9001"]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["standard_parallel_1",42.68333333333333],PARAMETER["standard_parallel_2",41.71666666666667],PARAMETER["latitude_of_origin",41],PARAMETER["central_meridian",-71.5],PARAMETER["false_easting",200000],PARAMETER["false_northing",750000],AUTHORITY["EPSG","26986"],AXIS["X",EAST],AXIS["Y",NORTH]]]'
  ],
  [
    'EPSG:4269',
    '+proj=gnom +lat_0=90 +lon_0=0 +x_0=6300000 +y_0=6300000 +ellps=WGS84 +datum=WGS84 +units=m +no_defs'
  ]
]);

그러면, 언제라도 다음처럼 사용할 수 있습니다.

var p = proj4('EPSG:4326', 'EPSG:4269');
var result = p.forward([-71, 41]);
// [-2690599.9886444192, 3662814.7663661353]

사실 위 코드의 첫번째 라인은 다음의 축약형입니다.

var p = proj4(proj4.defs('EPSG:4326'), proj4.defs('EPSG:4269'));

미리 정의된 이름을 가지는 투영변환은 EPSG:4326, EPSG:4269, EPSG:3857입니다. 아울러 EPSG:4326은 WGS84라는 이름으로도 정의되어 있으며, EPSG:3857은 EPSG:3758, GOOGLE, EPSG:900913, EPSG:102113이라는 다양한 이름으로도 정의되어 있습니다. 이에 대한 proj4js 라이브러리의 코드를 살펴보면 다음과 같습니다.

defs('EPSG:4326', "+title=WGS 84 (long/lat) +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees");
defs('EPSG:4269', "+title=NAD83 (long/lat) +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees");
defs('EPSG:3857', "+title=WGS 84 / Pseudo-Mercator +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");

defs.WGS84 = defs['EPSG:4326'];
defs['EPSG:3785'] = defs['EPSG:3857']; // maintain backward compat, official code is 3857
defs.GOOGLE = defs['EPSG:3857'];
defs['EPSG:900913'] = defs['EPSG:3857'];
defs['EPSG:102113'] = defs['EPSG:3857'];

아래는 한국의 좌표계를 예로 들어 proj4js의 설명을 담은 글입니다. 2013년도 글이라 현재 버전의 API와 맞지 않을 수 있습니다.

[GIS] 오픈소스, 자바스크립트 좌표계 변환 라이브러리, proj4js

끝으로 EPSG 코드를 통한 proj 및 wkt를 얻을 수 있는 사이트에 대한 글은 아래와 같습니다.

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