FingerEyes-Xr에서 속성값으로 도형 심벌 및 라벨문자열 설정하기

여러 개의 속성값으로 라벨 문자열을 조립하여 실제 표시되는 라벨을 결정하는 코드의 예입니다.

CustomLabelFormatter = Xr.Class({
    name: "CustomLabelFormatter",
    extend: Xr.label.ProgrammableLabelFormatter,
    requires: [Xr.label.ILabelFormatter],
    construct: function (layer) {
        this.superclass(layer);
        this._MGN_CD = -1;
        this._MGN_DT = -1;
        this._codeValues = {
            'C01': '시설A',
            'C02': '시설B',
            'P01': '시설C',
            'P03': '시설D',
            'R03': '시설E',
            'T01': '시설F'
        };
    },
    methods: {
        value: function (shapeRow, fieldSet, attributeRow) {
            if (this._EQMT_FIXPLC_RGN_SE_CD == -1) {
                this._EQMT_FIXPLC_RGN_SE_CD = fieldSet.fieldIndex("MGN_CD");
            }

            if (this._FST_REGI_TSP == -1) {
                this._FST_REGI_TSP = fieldSet.fieldIndex("_MGN_DT");
            }

            let code = attributeRow.valueAsString(this._MGN_CD);

            // 필드 2개의 값(_MGN_CD 필드의 코드값 + _MGN_DT 의 값)을 조합한 라벨 표시, 예: 시설B(2020-07-27 14:32:32)
            return this._codeValues[code] + "(" + attributeRow.valueAsString(this._MGN_DT) + ")";
        }
    }
});

var lyr = new Xr.layers.ShapeMapLayer("lyr", ...);
let label = lyr.label();
label.enable(true);

let formatter = new CustomLabelFormatter(lyr);
label.formatter(formatter);

lm.add(lyr);

다음은 속성값으로 도형의 스타일 심벌을 지정하는 코드의 예입니다.

CustomLayerTheme = Xr.Class({
    name: "CustomLayerTheme",
    extend: Xr.theme.ProgrammableShapeDrawTheme,
    requires: [Xr.theme.IShapeDrawTheme],
    construct: function (/* ShapeMapLayer */ layer) {
        this.superclass(layer);
        this._fieldIndex = -1;
        let codes = ['R01', 'P02', 'R03', 'P01', 'P02', 'T01', 'T02', 'C01', 'C02'];
        let colors = ['#f1c40f', '#f39c12', '#e67e22', '#e74c3c', '#c0392b', '#ff0000', '#00ff00', '#0000ff', '#ff00ff'];
        let symbols = [];
        let cntCodes = codes.length;
        for (let i = 0; i < cntCodes; i++) {
            let SDS = new Xr.symbol.ShapeDrawSymbol();
            SDS.brushSymbol().color(colors[i]).opacity(0.5);
            SDS.penSymbol().color(colors[i]).width(2);

            let symbol = {
                code: codes[i],
                symbol: SDS
            };

            symbols[i] = symbol;
        }

        this._symbols = symbols;
    },
    methods: {
/* ShapeDrawSymbol */ symbol: function (/* ShapeRow */ shapeRow, /* FieldSet */ fieldSet, /* AttributeRow */ attributeRow) {
            if (this._fieldIndex === -1) {
                this._fieldIndex = fieldSet.fieldIndex("MGN_CD");
            }

            let value = attributeRow.valueAsString(this._fieldIndex);
            let symbols = this._symbols;
            let symbol = undefined;
            let cntSymbols = symbols.length;
            for (var i = 0; i < cntSymbols; i++) {
                symbol = symbols[i];
                if (value === symbol.code) {
                    break;
                }
            }
            return symbol.symbol;
        },

/* boolean */ needAttribute: function () {
            return true;
        }
    }
});


var lyr = new Xr.layers.ShapeMapLayer("lyr", ...);

var newTheme = new CustomLayerTheme(lyr)
lyr.theme(newTheme);

lm.add(lyr);

NexGen, 공간 데이터의 분포경향 분석을 위한 밀도맵 기능

공간 분석 중, 밀도맵은 위치 데이터가 공간 상에 분포하는 경향을 매우 효과적으로 시각화할 수 있는 의사결정 지원 도구입니다. 위치 데이터의 공간 상의 위치 분포 뿐만 아니라 각 위치 데이터가 갖는 가중치값을 밀도 분석에 반영할 수 있어 위치 데이터와 통계 데이터를 조합할 수 있습니다.

밀도맵 기능의 소개를 위해 서울시의 인구 통계 데이터를 담고 있는 SHP 파일을 NexGen에 바로 불러와 밀도맵을 작성했습니다. 아래의 표는 사용한 인구 SHP 파일의 속성값에 대한 구조입니다. 통계청의 집계구 데이터를 받아 SHP 파일로 가공하였습니다.

이 데이터는 아래의 URL을 통해 다운로드 받으실 수 있습니다. 통계청에서 다운로드 받은 폴리곤 타입의 SHP 파일인 집계구 데이터와 TXT 파일인 2017년도 인구 데이터를 이용해 포인트 타입의 단일 SHP 파일로 직접 가공한 것입니다. 좌표계는 EPSG:5181입니다.



아래의 시연 동영상은 위의 인구 데이터를 활용한 NexGen의 밀도맵 기능에 대한 소개입니다.

웹 기반의 GIS 솔루션인 NexGen의 밀도맵 기능은 히트맵(HeatMap) 방식이 아닌 정규분포 방식의 알고리즘을 활용하여 고품질의 밀도맵을 작성할 수 있습니다.

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


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 문자열 얻기

케라스(Keras)의 get_file 함수

신경망 학습을 위한 데이터 준비를 위해, 인터넷 상의 파일을 다운로드 받아 압축을 풀 경우가 있습니다. 이때 내가 원하는 로컬 경로에 다운로드를 받고, 원하는 서브 디렉토리에 압축을 풀기 위해 다음의 코드가 사용될 수 있습니다.

import tensorflow as tf

path = 'D:/GeoAI/20200203'
tf.keras.utils.get_file(path + '/image.zip', 'http://where.net/data.zip', extract=True, cache_subdir='data', cache_dir=path)

결과적으로 인터넷 상에 존재하는 http://where.net/data.zip을 D:/GeoAI/20200203 디렉토리에 image.zip 파일명으로 다운로드 받고, 압축은 D:/GeoAI/20200203/data에 풀게 됩니다.

추가적으로 아래의 코드는 압축이 풀린 파일명 리스트를 얻는 코드입니다.

import pathlib
file_path = pathlib.Path(path + '/data/images')

file_paths = list(file_path.glob('*/*'))
print(file_paths[:10])

이렇게 얻은 파일들의 경로를 통해, 만약 해당 파일이 이미지 형식이라면 실제 화면상에 표시해 확인할 수 있는데, 해당 코드는 아래와 같습니다.

import matplotlib.pyplot as plt
import os

plt.figure(figsize=(12,12))
for i in range(9):
    plt.subplot(3,3,i+1)
    plt.imshow(plt.imread(file_paths[i]))
    plt.title(os.path.basename(file_paths[i]))
    plt.axis('off')
plt.show()

결과는 다음과 같습니다.