[GIS] 우리가 만들고, 우리가 직접 쓰는 솔루션, Xr

처음 GIS 분야에서 솔루션 개발로 사업을 하겠다고 했을때.. 솔루션을 기획하고 개발하면서 갖고 있던 생각 중에 하나는.. 이렇게 개발한 솔루션을 고객에게 제공하는 것에 머무르지 말고.. 직접 내부에서 사용하고, 우리가 기획한 서비스를 우리의 솔루션으로 개발하고.. 내부적으로 수행하는 GIS 업무에 필요한 작업 역시도 우리가 만든 솔루션으로 하는 것이였습니다.

공간 데이터 가공을 위한 듀라맵, 웹GIS 서비스를 위한 핑거아이즈, 모바일 GIS 개발을 위한 블랙포인트 등.. 직접 사용해 보고 직접 고객이 되어 불편한 것이 무엇인가.. 발생하는 오류는 무엇인지 등을 직접 경험해 보면서 그 품질을 높여 나가고 있습니다.


오늘도.. 프로젝트 수행에서 자체 개발한 솔루션만을 활용하여 공간 데이터를 구축하고 통계 데이터를 입력하고 있습니다. 문제 없이… 매우 편리하게.. 그리고 신속하게 물흐르듯… 작업을 할 수 있어 매우 기쁘네요.. ㅜ_ㅜ

솔루션에 문제가 발생하는 것을 해결할때의 그 고통스러움에 대한 댓가로.. 이러한 기쁨을 스스로 느껴 볼 수 있는 것도 큰 가치라고 생각합니다..

[GIS] FingerEyes-Xr에서 공간 데이터 편집에 대한 정리(Bridge Edit)

(주의) 이 글은 현재 핑거아이즈의 Bridge Edit 방식을 설명한 글입니다. 코드 작성을 위한 효율성을 목적으로 이 글의 내용이 실제 API와 달라질 수 있습니다. 이 글은 FingerEyes-Xr 버전 2.7을 기준으로 합니다.

핑거아이즈(FingerEyes-Xr)는 웹에서 공간 데이터를 편집할 수 있는 강력한 기능을 지원합니다. 편집에 대한 Undo/Redo는 물론 Vertex와 Segment에 대한 스냅핑(Snapping) 기능을 지원함으로써 편집에 대한 편리성을 사용자에게 제공합니다.

핑거아이즈는 공간 데이터 편집에 대해서 Direct Edit 방식과 Bridge Edit 방식을 제공합니다. Direct Edit은 공간 데이터를 편집하면 곧바로 DBMS에 반영되는 방식이고 Bridge Edit 방식은 편집된 공간 데이터를 곧바로 DBMS에 저장하지 않고 저장하기 중간 단계에 사용자의 확인을 통해 저장하는 방식입니다. Direct Edit 방식은 Bridge Edit 방식에 비해 개발이 쉽다는 장점이 있으나 편집을 위해 사용자의 계정 정보가 필요하다는 번거로움이 있습니다.

편집 대상이 되는 공간 데이터는 DBMS에 저장되어 있으며 실제 공간 데이터를 사용자에게 보여주기 위해 레이어(Layer)라는 개념을 통해 표현됩니다. DBMS에 연계된 공간 데이터에 대한 레이어가 XrShapeMapLayer입니다. Direct Edit 방식은 이 XrShapeMapLayer에 대해 바로 편집하며 DBMS에 반영시키는 것입니다. 이 글에서 설명할 Bridge Edit 방식은 XrShapeMapLayer를 바로 편집하는 것이 아니라 XrMashupLayer를 브리지(Bridge) 개념으로 하여 원하는 형태의 정보로 편집이 끝나면 DBMS에 반영하는 방식입니다.

이 포스트는 Bridge Edit 방식을 통해 공간 데이터를 편집하기 위한 내용을 정리한 글입니다.

먼저 편집할 공간 데이터에 대한 XrShapeMapLayer와 브리지(Bridge) 역활을 XrMashupLayer가 필요합니다. XrShapeMapLayer에 해당하는 객체를 _shapeLyr이라고 하고 XrMashupLayer에 해당하는 객체를 _mashupLyr이라고 하면 편집을 위한 초기화 코드는 다음과 같습니다.

_map.edit.targetLayer = _mashupLyr;
_map.addEventListener(XrMapMouseEvent.XRMAP_MOUSE_CLICK_EVENT, onMapClick);
_map.addEventListener(
    XrEditFeatureAddedNewEvent.XR_EDIT_FEATURE_ADDED_NEW_EVENT, onAddNewEvent);
_map.edit.editMode = false;

이러한 초기화 이후에 편집 기능을 구현할 수 있으며, 편집은 다음과 같은 기능으로 분류할 수 있습니다.

  1. 새로운 도형 추가
  2. 기존 도형의 편집
  3. 기존 도형의 삭제

1. 새로운 도형 추가

새롭게 편집할 도형이 포인트라고 할때, 새로운 도형포인트을 추가하기 위한 시작 코드는 다음과 같습니다.

_map.edit.editMode = true;
_mashupLyr.removeAllMashups();
_map.edit.addPoint(0);

위의 코드를 통해 사용자는 지도 위에 도형을 새롭게 그릴 수 있고 도형 그리기가 완료되면 편집을 위한 초기화 코드에서 추가한 이벤트인 XR_EDIT_FEATURE_ADDED_NEW_EVENT에 대한 실행 함수인 onAddNewEvent가 실행됩니다. 기본적인 편집에서는 이 이벤트에 코드를 추가할 필요가 없으나 만약 필요하다면 코드를 추가할 수 있으며 이 글에서는 사용하지 않습니다. 이렇게 새롭게 추가된 도형을 DBMS에 저장하는 코드는 다음과 같습니다.

var svc:XrService;
var params:Object;
svc = new XrQueryTableService(
    Global.GIS_SERVER, "mssqlserver", onFIDQueryCompleted, onFIDQueryError);
if(!svc.run({cache: false, sql: "SELECT (MAX(FID)+1) FID FROM SpatialDBTBL"}))
{
    mx.controls.Alert.show("FID Query invoke error");
}

위의 코드는 새롭게 추가된 공간데이터를 저장하기 전에 Feature ID를 얻기 이한 코드입니다. 새로운 Feature ID를 계산하는 방법은 다양하지만 여기서는 기존 ID 값중 최대값에 대해 +1 인 값을 사용하는 방법을 활용했습니다. Feature ID 값을 제대로 얻어오게 되면 onFIDQueryCompleted 함수가 호출하며 다음과 같습니다.

private function onFIDQueryCompleted(result:String):void
{
    XML.ignoreWhitespace = true;
    var xml:XML = new XML(result);
    var rows:XMLListCollection = new XMLListCollection(xml.row);
    var cntRows:int = rows.length;
    var fid:int;
 
    if(cntRows == 1) 
    {
        fid = parseInt(rows[0].FID);
    }
    else 
    {
        mx.controls.Alert.show("FID is Nothing, Error"); 
        return;
    }

    var svc:XrService = new XrUpdateShapeMapFromMashupService(
        _map.edit, _shapeLyr, _mashupLyr, onNewUpdateCompleted, onUpdateFail);
    var params:Object = { targetId:fid, sourceId:0 /*, attributes:attrs*/ };
   
    if(!svc.run(params)) 
    {
        mx.controls.Alert.show(
            "XrUpdateShapeMapFromMashupService params are invalid.");
    }     
}
   
private function onFIDQueryError():void
{
    trace("ERROR : FID Query");
}

실제 DBMS에 반영하기 위해서 XrUpdateShapeMapFromMashupServer를 사용합니다. 성공적으로 DBMS에 반영이 되면 onUpateCompleted가 실행되며 실패하면 onUpdateFail이 실행됩니다. 이 두 함수는 다음과 같습니다.

protected function onNewUpdateCompleted():void
{
    _map.edit.selectNone();
    _map.edit.editMode = false;
    _map.edit.editHistoryReset();
    _mashupLyr.removeAllMashups();

    _map.update();
}
   
protected function onUpdateFail(msg:String):void
{
    mx.controls.Alert.show("EDIT ERROR: " + msg);
}

2. 기존 도형의 편집

다음은 기존 도형을 편집하는 기능에 대한 내용을 살펴보겠습니다. 먼저 기존 도형을 편집하겠다는 시작 코드(CODE-A)는 다음과 같습니다.

_mashupLyr.removeAllMashups();
_map.edit.editMode = false;
_map.edit.editHistoryReset();

위의 코드는 편집하고자 하는 기존 도형을 마우스로 선택하기 위해 편집 모드를 false로 지정한 것입니다. 앞서 편집을 위한 초기화 코드 중에서 XRMAP_MOUSE_CLICK_EVENT 이벤트에 대한 호출 함수인 onMapClick를 등록하였습니다. 이 onMapClick 함수에서 마우스로 클릭한 지점에 존재하는 도형의 Feature ID를 얻습니다.

protected function onMapClick(event:XrMapMouseEvent):void
{
    var clickX:Number = event.mouseEvent.localX;
    var clickY:Number = event.mouseEvent.localY;
    var fid:int = _shapeLyr.getFIDByMousePoint(x, y);
    
    if(fid != -1)
    {
        var shp:IXrShape = _shapeLyr.shapeSet.rows[fid];
        var point:XrPointShape = shp as XrPointShape;
        if(point != null)
        {
            _map.edit.selectNone();
            _map.edit.editHistoryReset();

            if(_mashupLyr.getMashup(0) != null) _mashupLyr.removeAllMashups();

            var coord:XrCoordinate = 
                new XrCoordinate(point.points[0].x, point.points[0].y);
            var mashup:IXrMashup = new XrMarkerMashup(0, coord, false);
    
            mashup.lineSymbol = new XrLineSymbol();
            mashup.fillSymbol = new XrFillSymbol();
            mashup.markerSymbol = new XrRectangleMarkerSymbol();

            _mashupLyr.addMashup(mashup);
            _mashupLyr.updateItem(0, false);

            var attr:XrAttribute = _shapeLyr.attributeSet.rows[fid];
      
            _map.edit.setSketch(0);
        }
    }
}

위의 코드를 통해 마우스로 편집하고자 하는 도형을 선택할 수 있으며 편집할 수 있습니다. 단, 다른 도형을 선택하기 위해서는 다시 기존 도형을 편집하겠다는 시작 코드(CODE-A)를 다시 실행해 줘야 합니다. 이 부분이 Birdge Edit 방식의 단점 중 하나입니다. 이렇게 편집된 내용을 실제 DBMS에 저장하기 위해 다음 코드의 실행이 필요합니다.

svc = new XrUpdateShapeMapFromMashupService(
    _map.edit, _shapeLyr, _mashupLyr, onModifyUpdateCompleted, onUpdateFail);

params = { targetId:_targetFid, sourceId:0/*, attributes:_attrs*/ };

if(!svc.run(params)) 
{
    mx.controls.Alert.show(
        "XrUpdateShapeMapFromMashupService's params are invalid.");
}

성공적으로 편집한 내용이 DBMS에 반영되 onModifyUpdateCompleted가 호출됩니다.

protected function onModifyUpdateCompleted():void
{
    _shapeLyr.shapeSet.removeRow(_targetFid);
    _shapeLyr.attributeSet.removeRow(_targetFid);
 
    _map.edit.selectNone();
    _map.edit.editMode = false;
    _map.edit.editHistoryReset();
    _mashupLyr.removeAllMashups(); 
    _map.update();
}

3. 기존 도형의 삭제

이제 끝으로, 선택된 도형을 삭제하는 방법에 대해 살펴보겠습니다.

삭제할 도형을 선택하는 방법은 편집하고자 하는 도형을 선택하는 것과 동일하며, 일단 선택된 상태에서 다음 코드를 실행하면 삭제됩니다.

var svc:XrUpdateShapeMapFromMashupService = new XrUpdateShapeMapFromMashupService(
    _map.edit, _shapeLyr, _mashupLyr, onModifyUpdateCompleted, onUpdateFail);
     
var params:Object = {targetId:_targetFid, removal:true };
     
if(!svc.run(params)) 
{
    mx.controls.Alert.show("params are invalid.");
}

Direct Edit는 DBMS에 편집한 내용을 반영하기 전까지 여러개의 공간도형을 편집할 수 있습니다. 그러나 Bridge Edit는 Direct Edit와 다르게 오직 하나의 공간도형에 대해서 편집을 할 수 있고 다른 공간 도형을 편집하기 전에 DBMS에 반영을 시켜야 합니다. 편집 이력에 대해서는 DBMS에 반영되기 전까지만 Undo/Redo가 가능하므로 Direct Edit이 Bridge Edit에 비해 더 많은 공간 데이터에 대해서 반영이 가능합니다.

[GIS] 도로명 주소 UI 만들기 위한 SQL 문

국내에서 제공하는 공간데이터 중 행안부의 새주소 데이터를 이용해 도로명 주소 UI를 만들기 위한 SQL문을 정리해 보려 합니다. 행정구역은 서울시를 대상으로 하겠습니다. 도로명 주소 체계가 특정 지역에 대해 특이사항이 있는 것은 아니지만.. 일단 서울시의 경우 도로명 주소는 구를 선택하면 해당 구에 소속된 도로명들이 존재합니다.

서울시 하나의 구에 대한 도로명은 개수는 상당히 많아 선택된 구에 대한 도로명을 하나의 컴보박스 컨트롤을 통해 집어 넣으면 사용자가 선택하기 어렵습니다. 해서 다음의 UI처럼 선택된 구에 대한 도로명을 ㄱ,ㄴ,ㄷ 과 같은 첫자에 대한 자음으로 분류하는 방법을 사용합니다.

사용자 삽입 이미지
위의 그림에서 A Part에는 ㄱ,ㄴ,ㄷ과 같은 도로명에 대한 첫자의 자음의 리스트이고 B Part는 선택 자음에 대한 도로명의 리스트입니다. 행안부에서 제공하는 도로명 DB는 tl_sprd_manage입니다. 먼저 A Part를 구성하기 위한 SQL(MSSQL Server 기준) 문은 다음과 같습니다.

select distinct GetConsonant(SUBSTRING(RN,1,1)) 
from tl_sprd_manage 
where SIG_CD = ‘{SIG_CD} ' 
group by RN

{SIG_CD}는 서울시의 구에 대한 5자리의 행정코드 값입니다. 또한 위의 SQL 문에는 GetConsonat 함수가 사용되었습니다. 이 함수에 대한 정의는 다음과 같습니다.

create FUNCTION GetConsonant
(
  @STR AS NVARCHAR(100)
)
RETURNS NVARCHAR(100)
AS
BEGIN
  DECLARE @STR_N NVARCHAR(100)
  DECLARE @STR_S NVARCHAR(100)
  WHILE (LEN(@STR) > 0)
  BEGIN
    SET @STR_N = SUBSTRING(@STR, 1, 1)
    SET @STR = SUBSTRING(@STR, 2, LEN(@STR))
    SET @STR_S = ISNULL(@STR_S, '') + 
      (CASE WHEN @STR_N BETWEEN '가' AND '깋' THEN 'ㄱ'
          WHEN @STR_N BETWEEN '나' AND '닣' THEN 'ㄴ'
          WHEN @STR_N BETWEEN '다' AND '딯' THEN 'ㄷ'
          WHEN @STR_N BETWEEN '라' AND '맇' THEN 'ㄹ'
          WHEN @STR_N BETWEEN '마' AND '밓' THEN 'ㅁ'
          WHEN @STR_N BETWEEN '바' AND '빟' THEN 'ㅂ'
          WHEN @STR_N BETWEEN '사' AND '싷' THEN 'ㅅ'
          WHEN @STR_N BETWEEN '아' AND '잏' THEN 'ㅇ'
          WHEN @STR_N BETWEEN '자' AND '짛' THEN 'ㅈ'
          WHEN @STR_N BETWEEN '차' AND '칳' THEN 'ㅊ'
          WHEN @STR_N BETWEEN '카' AND '킿' THEN 'ㅋ'
          WHEN @STR_N BETWEEN '타' AND '팋' THEN 'ㅌ'
          WHEN @STR_N BETWEEN '파' AND '핗' THEN 'ㅍ'
          WHEN @STR_N BETWEEN '하' AND '힣' THEN 'ㅎ'
          ELSE @STR_N END)
       END
  RETURN @STR_S
END

이제 B Part에 대한 SQL문을 살펴보면 다음과 같습니다.

select RN, RN_CD from tl_sprd_manage where SIG_CD = ‘{SIG_CD}' 
and GetConsonant(SUBSTRING(RN,1,1))='ㄱ' 
group by RN, RN_CD order by RN

위의 SQL은 A Part에서 ‘ㄱ’을 선택했을때 ‘ㄱ’으로 시작하는 도로명의 리스트를 뽑아냅니다. 참고로 RN은 도로명이며 RN_CD는 도로명에 대한 코드값입니다.

[GIS] SHP2SQL ㅡ MSSQLServer Version

SHP 파일에 대한 속성정보와 MBR 정보를 MSSQL Server의 DB 테이블에 저장해 주는 SQL문을 생성해 주는 툴입니다. 포인트 SHP 파일은 MBR 정보에 대해서 포인트(X, Y) 값을 저장하고 폴리라인과 폴리곤의 경우 MinX, MinY, MaxX, MaxY 값을 저장해 줍니다.

(주)지오서비스의 공간서버인 GeoService-Xr이 공간 데이터에 대해 MSSQLServer를 지원하게 되면서 부차적인 목적으로 필요하여 만들어진 툴입니다. 이 프로그램은 듀라맵(DuraMap-Xr)을 기반으로 SHP 파일을 처리하므로 듀라맵이 먼저 설치되어 있어야 합니다.

사용자 삽입 이미지
위의 화면에서처럼 속성 데이터에 대해 선택할 수 있습니다. 그리고 테이블을 생성할 CREATE SQL문과 레코드를 입력할 INSERT SQL에 대해서 따로 저장하도록 되어 있습니다. 위의 화면에 대한 실행 결과로써 CREATE SQL 문에 대한 결과는 다음과 같습니다.

사용자 삽입 이미지
그리고 INSERT SQL 문에 대한 결과는 다음과 같습니다.

사용자 삽입 이미지
실행 파일과 소스 파일은 아래의 경로를 통해 다운로드 받으시기 바랍니다. 비록 이 툴은 MSSQL Server에 대한 SQL문을 생성하지만 다른 DBMS의 경우 소스코드를 약간 변경하여 다른 DBMS에 맞는 SQL 구문을 얻을 수 있습니다.

[GIS] 공간 데이터 좌표 변환툴 – XrProjection

이 툴에 대한 최신 버전은 XrProjection v3.1 을 통해 다운로드 받아 사용하시기 바랍니다.

개발자 피드백

  • EPSG900913(구글좌표계)를 지원합니다. 구글좌표계는 WGS84 타원체를 사용하므로 WGS84 타원체가 아닌 좌표계 간의 변환에서는 반드시 변환 파라메터를 지정하셔야 합니다 ㅡ 2013/07/23
  • WGS80 타원체의 경위도에 대한 Bessel 타원체/GRS80 타원체의 경위도에 대한 상호 좌표변환은 반드시 변환 파라메터 사용 여부를 체크해야 옳바르게 변환됩니다 ㅡ 2013/05/30
  • 행안부 새주소 좌표계(UTM-K)와 관련된 좌표계 변환 정확도 문제 제거하였습니다 ㅡ 2013/03/22
  • WGS84 타원체에서 다른 좌표계로의 변환 정확도 문제를 제거하였습니다 ㅡ 2012/10/28

공간 데이터에 대한 좌표 변환툴입니다. SHP 파일에 대한 좌표 변환과 폴더에 저장된 모든 파일에 대한 좌표 변환 그리고 좌표 하나에 대한 좌표 변환 끝으로 Degree와 DMS 간의 변환 기능을 제공합니다. 이와 함께 좌표 변환에 사용되는 3 파라메터와 7 파라메터를 지정할 수도 있습니다.

각 기능에 대한 UI는 탭으로 독립되어 있어서 사용하기에 쉽고 직관적입니다. 이 프로그램에 대한 소스 코드와 실행 파일을 별도로 분리하여 아래 링크를 통해 다운로드 받을 수 있습니다. 이 프로그램은

듀라맵과 .NET 2.0 프레임워크를 사용합니다.

먼저 좌표변환시 사용할 3 파라메터 또는 7 파라메터를 지정하는 UI입니다. 실제 업무에서 있어서 특별한 경우가 아니라면 이 파라메터의 지정은 하지 않으며 공간 데이터를 제공하는 곳에서 특별한 언급이 있는 경우에 사용하시기 바랍니다.

사용자 삽입 이미지
다음은 하나의 SHP 파일에 대한 좌표 변환 기능입니다. 변환하고자 하는 SHP 파일과 변환하고자 하는 SHP 파일의 원래 좌표계를 지정하고 변환되어 저장 출력될 SHP 파일과 변환될 좌표계를 지정하면 됩니다.

사용자 삽입 이미지
다음은 폴더 단위로 좌표변환을 하는 기능입니다. 지정한 폴더에 저장된 모든 SHP 파일이 좌표 변환되어 원하는 출력 폴더로 저장됩니다. 많은 SHP 파일을 한번에 좌표 변환할때 유용하게 사용할 수 있는 기능입니다.

사용자 삽입 이미지
다음은 하나의 좌표에 대한 변환 기능입니다. 하나의 좌표의 변환만을 해보거나 좌표 변환 기능을 테스트해 볼 때 유용한 기능입니다.

사용자 삽입 이미지
끝으로 하나의 Degree(도) 단위 값의 각도를 DMS(Degree, Minute, Second)의 3개의 값으로 분리하거나 이와 반대로 변환해 주는 기능입니다.

사용자 삽입 이미지
이 프로그램은 맵엔진인 듀라맵을 이용해 개발되었습니다. 실행을 위해 먼저 듀라맵을 시스템에 등록하셔야 합니다. 듀라맵에 대한 소개는 다음 UR을 통해 살펴보시기 바랍니다.