FingerEyes-Xr에서 폰트(Font)를 이용한 마커 심벌(Marker Symbol) 지정하기

FingerEyes-Xr에서 포인트 타입의 수치지도는 다양한 형태로 표현될 수 있습니다. 원이나 사각형과 같은 단순 도형에서 이미지나 폰트를 통한 텍스트 표현까지 가능합니다. 특히 이미지는 애니메이션 GIF인 경우 애니메이션 효과도 그대로 표현됩니다.

이 글은 포인트 타입의 수치지도를 폰트를 통한 텍스트 표현을 위한 API에 대한 내용을 정리한 글입니다.

포인트를 폰트를 통한 텍스트로 표현하는 이유 중에 하나는 웹에서 딩벳(Dingbat) 폰트에서 일반적이지 않은 특별하게 디자인된 텍스트로 포인트 심벌을 표현하기 위함입니다. 아래의 화면은 멘홀에 대한 단순한 포인트를 하수설비에 대해 특별이 만들어진 폰트로 표현된 지도입니다.

노란색의 채움과 빨간색의 외곽선으로 표시된 아이콘 모양의 심벌이 바로 포인트 수치지도에 대해 표현된 텍스트인데요. 이처럼 수치지도 레이어에 폰트 심벌을 적용하는 코드는 아래와 같습니다.

var SB101 = new Xr.layers.ShapeMapLayer("SB101", {
    url: "http://168.192.76.10:8080/Xr?layerName=SB101"
});

var theme = SB101.theme();

var marker = new Xr.symbol.TextMarkerSymbol();
marker.text('1'); // Dingbat은 1이지만, 1이 아닌 뭔가 특별한 모양의 문자로 표시된다.

theme.markerSymbol(marker);

marker.fontSymbol().fontFamily("ffHasu").color("#ffff00").strokeColor("#ff0000").strokeWidth(3).size(36);

lm.add(SB101);

위의 코드 중 12번 코드에 fontFamily 함수를 통해 “ffHasu” 값을 지정하고 있습니다. 이 “ffHasu” 아래의 style을 통해 정의된 값입니다. 추가적으로 폰트가 사용자의 PC에 설치되어 있지 않을 경우, 웹에서 해당 폰트를 내려받아 웹페이지에서 사용할 수 있는데요. 아래의 코드는 특정 폰트를 내려받아 사용할 수 있도록 있도록 해주기도 합니다.

@font-face {
    font-family: ffHasu;
    src: url(../css/Hist_Hasu.woff);
}

FingerEyes-Xr에서 속성값에 따라 라벨 텍스트에 대한 그리기 심벌 변경하기

일반적으로는 하나의 레이어에 대해서 라벨에 대한 텍스트는 하나의 심벌로 통일해서 아래처럼 표시합니다.

위에 화면에 대한 소스 코드(코드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>
        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"
                }
            );
            
            // Label Setting           
            var label = lyr.label();
            label.enable(true);
            label.formatter().fieldName("first_nv_n");
            label.theme().symbol().size(40).strokeWidth(5);
            // .

            var theme = lyr.theme();
            theme.penSymbol().color("black").width(3);
            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>

그러나 속성값에 따라 라벨 텍스트의 그리기 심벌을 달리해야 할 필요가 있을 수 있는데요. 예를 들어 인구수에 따라 라벨의 글자 색상을 변경하거나 폰트의 크기를 변경하여 표현할 수 있습니다. 이를 위해서는 다음과 같은 클래스의 정의가 필요합니다.

CustomLabelTheme = Xr.Class({
    name: "CustomLabelTheme",
    extend: Xr.theme.ProgrammableLabelDrawTheme,
    requires: [Xr.theme.ILabelDrawTheme],

    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 sizes = [20, 25, 30, 40, 50];
        var symbols = [];

        for (var i = 0; i < stepCount; i++) {
            var FS = new Xr.symbol.FontSymbol();
            FS.color(colors[i]).size(sizes[i]).strokeWidth(sizes[i]/10);
                    
            var symbol = {
                fromValue: minValue + (i * stepValue),
                toValue: minValue + ((i + 1) * stepValue),
                symbol: FS
            };

            symbols[i] = symbol;
        }

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

    methods: {
        /* FontSymbol */ 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;
        }
    }
});

위의 CustomLabelTheme 클래스는 표시할 라벨의 텍스트의 심벌을 속성값에 따라 변경할 수 있는 기능을 제공하며, 이를 위해서 Xr.theme.ProgrammableLabelDrawTheme 클래스를 상속하고 Xr.theme.ILabelDrawTheme 인터페이스를 구현해야 합니다. 이 클래스의 생성자에서는 라벨 텍스트의 표시를 위해 사용할 폰트 심벌을 미리 생성해 두고 있습니다. 폰트 심벌의 생성 규칙은 속성값의 최대값과 최소값을 이미 알고 있다고 할때(11번과 12번 코드에 매직넘버로 심어 두었지만 실제 적용시에는 이러한 최대, 최소값은 DBMS의 Query를 통해 얻어야 함), 이 값의 범위를 5개의 구간으로 구분하고 각 구간에 대해서 텍스트의 색상과 크기를 이용하여 폰트 심벌을 미리 정의해 배열에 담고 두고 있습니다. 그리고 라벨을 실제로 그릴때 사용할 폰트의 심벌을 얻기 위해서는 symbol 함수가 실행되어 폰트 심벌이 반환되는데요. symbol 함수를 살펴보면, 속도 향상을 위해 폰트 심벌을 결정할 필드의 인덱스를 계산해 두고, 해당 필드값을 이용해 생성자에서 미리 생성해 두었던 5개의 폰트 심벌 중 하나를 선택해 반환하고 있습니다. 이 클래스를 적용하기 위해서는 코드A의 41번 코드 대신 아래의 코드로 대체해야 합니다.

var newTheme = new CustomLabelTheme(lyr);            
label.theme(newTheme);

이제 실행 결과에 대한 이미지를 살펴보면 다음과 같습니다.

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);