FingerEyes-Xr에서 여러 개의 속성값 합쳐 라벨 표시하기

FingerEyes-Xr에서 수치지도에 대한 라벨을 표시할 때, 일반적으로 하나의 필드명을 지정해 지정된 필드값을 라벨로 표시합니다. 아래는 어떤 수치지도의 first_nv_n 필드를 라벨로 지정해 지도를 표시하는 코드(코드A)의 예입니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />

    <style>
        body {
            margin: 0px;
            padding: 0px;
        }

        #map {
            top: 0px;
            left: 0px;
            position: absolute;
            width: 100%;
            height: 100%;
            border: none;
            outline: none;
        }
    </style>

    <script src="../../scripts/fingereyes-xr/Xr.js"></script>

    <script>
        var map = null;

        function onLoad() {
            map = new Xr.Map("map", {});

            var lyr = new Xr.layers.ShapeMapLayer("lyr",
                {
                    url: "http://168.192.76.10:8080/Xr?layerName=li_a@test"
                }
            );
            
            var theme = lyr.theme();
            theme.penSymbol().color("black").width(4);
            theme.brushSymbol().color("lightgray");

            // Label Setting
            var label = lyr.label();

            label.enable(true);
            label.formatter().fieldName("first_nv_n");
            label.theme().symbol().size(40).strokeWidth(5);
            // .


            var lm = map.layers();
            lm.add(lyr);

            map.onLayersAllReady(onLayersReady);
            window.addEventListener("resize", onResize);
        }

        var bFinishResizing = true;

        function onResize() {
            if (bFinishResizing) {
                bFinishResizing = false;

                setTimeout(function () {
                    var newWidth = window.innerWidth;
                    var newHeight = window.innerHeight;

                    map.resize(newWidth, newHeight);
                    map.update();

                    bFinishResizing = true;
                }, 500);                
            }
        }

        function onLayersReady() {
            var cm = map.coordMapper();
            var lyr = map.layers("lyr");
            var mbr = lyr.MBR();

            cm.zoomByMBR(mbr);

            map.update();
        }
    </script>

    <title></title>
</head>

<body onload="onLoad()">
    <div id="map" />
</body>
</html>

위의 코드를 실행하면 아래의 결과를 볼 수 있습니다.

위처럼 단순히 하나의 필드가 아닌 여러 개의 필드 값을 조합하여 라벨로 표시하고자 할때가 있습니다. 아래의 화면은 first_nv_n와 sum_ho_res에 대한 2개의 필드값을 조합하여 라벨을 표시하고 있는 예입니다.

위의 화면을 보면 지역에 대한 명칭(first_nv_n)과 해당 지역의 인구수(sum_ho_res)를 단위와 함께 표시하고 있는데요. 이를 위해 다음과 같은 사용자 정의 클래스 코드가 필요합니다.

CustomLabelFormatter = Xr.Class({
    name: "CustomLabelFormatter",
    extend: Xr.label.ProgrammableLabelFormatter,
    requires: [Xr.label.ILabelFormatter],

    construct: function (layer) {
        this.superclass(layer);
        this._idx_first_nv_n = -1;
        this._idx_sum_ho_res = -1;
    },

    methods: {
        value: function (shapeRow, fieldSet, attributeRow) {
            if (this._idx_first_nv_n == -1) {
                this._idx_first_nv_n = fieldSet.fieldIndex("first_nv_n");
            }

            if (this._idx_sum_ho_res == -1) {
                this._idx_sum_ho_res = fieldSet.fieldIndex("sum_ho_res");
            }

            var first_nv_n = attributeRow.valueAsString(this._idx_first_nv_n);
            var sum_ho_res = attributeRow.valueAsString(this._idx_sum_ho_res);

            return first_nv_n + "(" + sum_ho_res + "명)";
        }
    }
});

위의 클래스는 라벨에 대한 형식을 지정할 수 있는 기능을 갖고 있으며, 이처럼 라벨의 형식을 지정하기 위한 기능을 갖는 클래스는 Xr.label.ProgrammableLabelFormatter 클래스를 상속받고 Xr.label.ILabelFormatter 인터페이스를 구현해야 합니다. 이 클래스에서 실제 라벨의 값에 대한 문자값을 반환해 주는 함수는 value인데요. 이 함수를 살펴보면, 속도 향상을 위해 사용할 필드의 인덱스 값을 미리 저장해 두고 실제 필드의 값을 얻어와 원하는 형태로 문자값을 구성해 반환해 주고 있습니다. 이제 이 클래스를 사용하기 위해 코드A의 45번 코드를 다음 코드로 대체해주면 됩니다.

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

FingerEyes-Xr에서 주제도(Theme Map) 표현

FingerEyes-Xr for HTML5에서 수치지도 레이어를 속성값에 따라 표현 심벌을 변경하여 주제도를 표현하는 기능에 대한 예제 코드를 정리해 봅니다.

먼저 다음과 같은 수치지도 레이어가 있다고 하겠습니다.

위의 결과를 표출하는 코드(코드A)는 아래와 같습니다.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />

    <style>
        body {
            margin: 0px;
            padding: 0px;
        }

        #map {
            top: 0px;
            left: 0px;
            position: absolute;
            width: 100%;
            height: 100%;
            border: none;
            outline: none;
        }
    </style>

    <script src="../../scripts/fingereyes-xr/Xr.js"></script>

    <script>
        var map = null;

        function onLoad() {
            map = new Xr.Map("map", {});

            var lyr = new Xr.layers.ShapeMapLayer("lyr",
                {
                    url: "http://168.192.76.10:8080/Xr?layerName=li_a@test"
                }
            );
            
            var theme = lyr.theme();
            theme.penSymbol().color("black");
            theme.brushSymbol().color("lightgray");

            var lm = map.layers();
            lm.add(lyr);

            map.onLayersAllReady(onLayersReady);

            window.addEventListener("resize", onResize);
        }

        var bFinishResizing = true;

        function onResize() {
            if (bFinishResizing) {
                bFinishResizing = false;

                setTimeout(function () {
                    var newWidth = window.innerWidth;
                    var newHeight = window.innerHeight;

                    map.resize(newWidth, newHeight);
                    map.update();

                    bFinishResizing = true;
                }, 500);                
            }
        }

        function onLayersReady() {
            var cm = map.coordMapper();
            var lyr = map.layers("lyr");
            var mbr = lyr.MBR();

            cm.zoomByMBR(mbr);

            map.update();
        }
    </script>

    <title></title>
</head>

<body onload="onLoad()">
    <div id="map" />
</body>
</html>

이제 위에서 본 단순한 수치지도 표현을 속성값에 따라 주제도로 표현해 보도록 하겠습니다. 즉, 아래처럼 말입니다.

위와 같은 주제도 표현을 위해서 도형에 대한 속성값에 따라 채움색을 다르게 지정하고 있는데요. 사용한 속성필드는 sum_ho_res입니다. DBMS에서 이 레이어의 sum_ho_res에 대한 최대값과 최소값을 얻어보면 각각 13과 104인데요. 이 값의 구간을 5개로 나눠 색상을 지정하고, 각 값에 대해서 해당 구간의 색상으로 레이어를 그려주게 되면 주제도가 완성됩니다. 이러한 주제도를 표현하기 위해 속성값에 대한 그리기 심벌을 지정해주기 위한 클래스는 아래와 같습니다. (코드B)

CustomLayerTheme = Xr.Class({
    name: "CustomLayerTheme",
    extend: Xr.theme.ProgrammableShapeDrawTheme,
    requires: [Xr.theme.IShapeDrawTheme],

    construct: function (/* ShapeMapLayer */ layer) {
        this.superclass(layer);

        this._fieldIndex = -1;

        var minValue = 13;
        var maxValue = 104;
        var stepCount = 5;
        var stepValue = (maxValue - minValue) / stepCount; 
        var colors = ['#f1c40f', '#f39c12', '#e67e22', '#e74c3c', '#c0392b'];
        var symbols = [];

        for (var i = 0; i < stepCount; i++) {
            var SDS = new Xr.symbol.ShapeDrawSymbol();

            SDS.brushSymbol().color(colors[i]);
            SDS.penSymbol().color('#ffffff');
            SDS.penSymbol().width(2);
                    
            var symbol = {
                fromValue: minValue + (i * stepValue),
                toValue: minValue + ((i + 1) * stepValue),
                symbol: SDS
            };
                    
            symbols[i] = symbol;
        }

        this._symbols = symbols;
        this._stepCount = stepCount;
    },

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

            var value = attributeRow.valueAsString(this._fieldIndex);
            var stepCount = this._stepCount;
            var symbols = this._symbols;
            var symbol = undefined;

            for (var i = 0; i < stepCount; i++) {
                symbol = symbols[i];

                if (value >= symbol.fromValue && value < symbol.toValue) {
                    break;
                }
            }

            return symbol.symbol;
        },

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

수치지도에 대한 주제도를 정의하기 위해서는 Xr.theme.ProgrammableShapeDrawTheme 클래스를 상속하고 Xr.theme.IShapeDrawTheme 인터페이스를 구현 해야 합니다. 이렇게 만든 클래스가 바로 CustomLayerTheme입니다. 이 클래스의 생성자에서 11번~32번까지의 코드는 sum_ho_res 속성값 범위에 대해서 최대값과 최소값(실제 개발에서는 SQL Query를 통한 해당값을 얻어와야 함)을 이용해 5개의 구간으로 나누고 각 구간에 대한 그리기 심벌(채움색과 선색) 객체를 미리 생성해 두고 있습니다. 또한 도형을 그리기 위한 심벌을 반환해주는 symbol 함수에서는 속도 향상을 위해 sum_ho_res 필드에 대한 필드 인덱스를 미리 계산해 두고, 그릴 대상이 되는 도형의 sum_ho_res 필드값을 얻어, 그 값에 해당하는 심벌을 반환합니다. 이제 이 CustomLayerTheme 클래스를 해당 레이어에 지정하기 위해서는 코드A에서 37번-39번의 코드 대신 아래의 코드로 대체해야 합니다.

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

CentOS 7에서 PostgreSQL 10.2, PostGIS 2.4.3 설치(인터넷 환경)

# postgresql10, postgis YUM 저장소 업데이트
rpm -Uvh https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm

위의 url은 변경될 수 있으며 2020년 8월 31일에는 rpm -Uvh https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm로 하여 설치를 진행했으며, postgresql은 10.10.14를, postgis는 3.0.2를 설치하였음.

# 데이터베이스 설치
yum install postgresql10-server.x86_64 postgresql10

# 데이터베이스 저장소 생성
cd /usr/pgsql-10/bin/
./postgresql-10-setup initdb

# 서비스 실행
systemctl enable postgresql-10
systemctl enable postgresql-10.service
systemctl start postgresql-10.service

# 5432 포트 방화벽 허용
firewall-cmd –zone=public –add-port=5432/tcp

# 외부 접속 허용
cd /var/lib/pgsql/10/data
vi postgresql.conf
(편집 내용)
listen_address = “*”

# 암호설정
su – postgres
psql
\password postgres
\q
su – root

# 외부 접속을 위한 보안 설정
cd /var/lib/pgsql/10/data
vi pg_hba.conf
(편집 내용)
local all all peer 문자열을 local all all md5 로 변경
host all all 127.0.0.1/32 ident 문자열을 host all all 0.0.0.0/0 md5 로 변경
host all all ::1/128 ident 문자열을 host all all ::1/128 md5 로 변경

# PostGIS 설치
yum install epel-release
yum install postgis24_10.x86_64
systemctl restart postgresql-10.service

FingerEyes-Xr에서 코드를 통한 지도 이동시 깜빡거리는 현상 제거

FingerEyes-Xr은 웹 기반에서 공간 데이터를 편집할 수 있도록 도형 데이터를 클라이언트에서 직접 렌더링하여 표시합니다. 즉 배경지도는 Image로 표시하고 그외 수치지도 레이어는 SVG와 같은 그래픽 요소로 표시됩니다.

이러한 구조로 인해 마우스 등을 통한 지도 이동 시에는 문제가 없으나, 코드를 통한 지도 이동시에 수치지도 레이어가 깜빡 거리는 현상이 발생할 수 있습니다. 그러나 움직이는 물체를 지도에서 추적하거나 GPS 등을 이용해 내 위치를 중심으로 지도를 이동시키는 등의 기능을 개발할때 이러한 깜빡거리는 현상은 사용자에게 좋지 않은 경험을 제공합니다.

그러나 FingerEyes-Xr에서도 움직이는 물체나 GPS 등을 통한 지도 이동 등과 같은 기능에서 자연스럽게 지도를 깜빡임없이 이동시키는 기능의 구현이 가능합니다. 이럴때 사용할 수 있는 코드는 아래와 같습니다.

function smoothMapMoveByPixel(map, px, py) {
    var cm = map.coordMapper();

    cm.moveMapByViewOffset(px, py);
    map.update(Xr.MouseActionEnum.MOUSE_DRAG_END, px, py);
}
                        
function smoothMapMove(map, newX, newY) {
    var cm = map.coordMapper();
    var prePt = cm.currentCenter();

    cm.moveTo(newX, newY);

    var deltaX = cm.viewLength(newX - prePt.x);
    var deltaY = cm.viewLength(newY - prePt.y);

    if (newX > prePt.x) deltaX = -deltaX;
    if (newY < prePt.y) deltaY = -deltaY;

    map.update(Xr.MouseActionEnum.MOUSE_DRAG_END, deltaX, deltaY);
}

함수가 2개인데요. 먼저 smoothMapMoveByPixel는 픽셀 단위값 만큼 지도를 자연스럽게 이동시키는 함수이고, smoothMapMove는 이동하고자 하는 위치를 지도의 절대좌표값으로 해서 자연스럽게 이동하는 함수입니다. 이중 smoothMapMove 함수의 사용예는 아래와 같습니다.

setInterval(function () {
    var map = ...;
    var cm = map.coordMapper();
    var prePt = cm.currentCenter();
    var newX = prePt.x + 2; 
    var newY = prePt.y - 2;

    smoothMapMove(map, newX, newY);
}, 100);

위의 코드는 0.1초마다 지도를 우측하단으로 x와 y축 모두에 대해 2미터만큼 이동시키는 것으로, 실제 시스템에 적용한 결과 화면은 아래와 같습니다.

보시는 것처럼 깜빡거림없이 자연스럽게 지도가 이동하는 것을 볼 수 있는데요. 만약, 구성 레이어가 많거나 해서 깜빡거림이 여전이 발생한다면 지도 이동 반복 주기에 대한 시간을 늘려 테스트 해보기 바랍니다.