Java에서 POST 방식으로 GeoService-X에 SQL 쿼리 요청

GeoService-Xr 공간서버에 직접 Java 언어로 SELECT 구문과 쿼리를 요청하는 코드 Keeping ..

try {
    URL url = new URL("http://X.X.X.X:8078/Xr?sql|oracle|0");
    HttpURLConnection httpConn = (HttpURLConnection)url.openConnection();
    httpConn.setUseCaches(false);
    httpConn.setDoOutput(true);
    httpConn.setRequestMethod("POST");
    OutputStream outputStream = httpConn.getOutputStream();
    String sql 
        = "SELECT IDN, CDE, MNG FROM TBLNAME WHERE IDN=2437";
    byte[] bytesSql = sql.getBytes();
    ByteBuffer bb = ByteBuffer.allocate(bytesSql.length + 1 + 4);
			
    bb.order(ByteOrder.BIG_ENDIAN);
    bb.putInt(bytesSql.length + 1);
    bb.put(bytesSql);
    bb.put((byte)0);
    bb.flip();
			
    byte[] bytes = bb.array();
    outputStream.write(bytes);
    outputStream.close();
			
    int responseCode = httpConn.getResponseCode();
    if(responseCode == HttpURLConnection.HTTP_OK) {
        BufferedReader reader 
            = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));
        System.out.println("Server's response: ");
        while(true) {
            String response = reader.readLine();
            if(response == null) break;
            System.out.println("\t" + response);
        }
    } else {
        System.out.println("Server returned non-Ok code: " + responseCode);
    }
} catch (Exception e) {
    e.printStackTrace();
}

[GIS] 자바 기반의 오픈소스를 이용한 좌표계 변환 정리

안드로이드 기반의 GIS 솔루션인 블랙포인트(BlackPoint-Xr)에는 다양한 좌표계간의 변환을 위한 클래스가 제공됩니다. 이 클래스들은 이미 다음 포스트를 통해 오픈소스 형태로 꽤 오래전부터 소개해 드렸는데요. 이번 기회에 다시한번 종합적으로 정리를 해봅니다.

이 좌표 변환을 위한 클래스를 소개하기 위해서 2가지 시나리오를 정하겠습니다. 첫째는 동일한 타원체를 갖는 서로 다른 좌표계간의 좌표변환이고 둘째는 서로 다른 타원체를 갖는 서로 다른 좌표계간의 좌표변환입니다. 이렇게 두개의 예를 드는 이유는 두개의 좌표계가 서로 다른 타원체를 갖느냐 갖지 않느냐에 따라 좌표변환의 과정이 매우 달라지기 때문입니다.

앞서 언급한 두가지 시나리오 중에서 첫번째인 동일한 타원체를 갖는 서로 다른 좌표계간의 좌표변환으로써 Bessel1841 타원체인 UTMK 좌표계에서 역시 Bessel1841 타원체를 갖는 카텍 좌표계로 변환하는 코드를 예로 살펴보겠습니다. 이 두 좌표계 간의 변환은 크게 2가지로 구분됩니다.

  1. UTMK 좌표계를 Bessel1841의 경위도 좌표계로 변환
  2. Bessel1841의 경위도 좌표를 카텍 좌표계로 변환

먼저 1. 번에 대한 코드는 다음과 같습니다.

String[] projParamsUTMK_B = {   
    "+proj=tmerc",   
    "+lat_0=38N",   
    "+lon_0=127.50289E",   
    "+ellps=bessel",   
    "+x_0=1000000",   
    "+y_0=2000000",   
    "+k=0.9996",   
    "+unit=m"  
};   
			    		     
Projection projUTMK_B 
    = ProjectionFactory.fromPROJ4Specification(projParamsUTMK_B);
			    		
Point2D.Double in = new Point2D.Double(986967, 1817577);
Point2D.Double out = new Point2D.Double();
projUTMK_B.inverseTransform(in, out);

1~10번 코드는 UTMK 좌표계를 정의하는 PROJ.4 형식의 파라메터입니다. 전세계에 존재하는 대부분의 좌표계는 위와 같은 파라메터로 구성이 가능합니다. 위의 코드는 UTMK 좌표계인 (986967, 1817577)를 in 객체에 담아 카텍으로 변환하여 out 객체에 저장하는 코드입니다. 이제 2. 번에 대한 코드는 다음과 같습니다.

String[] projParams_KATECH = {   
    "+proj=tmerc",   
    "+lat_0=38N",   
    "+lon_0=128E",   
    "+ellps=bessel",   
    "+x_0=400000",   
    "+y_0=600000",   
    "+k=0.9999",   
    "+unit=m"  
};   
			        	     
Projection projKatech 
    = ProjectionFactory.fromPROJ4Specification(projParams_KATECH);

in.x = out.x;
in.y = out.y;
projKatech.transform(in, out);

이 코드는 1. 에서 얻는 경위도 좌표가 담긴 out 객체를 다시 in 객체에 복사하고 카텍 좌표계로 변환합니다. 최종적으로 out 객체에 변환된 카텍 좌표가 저장되어 있습니다.

두번째 시나리오는 Bessel1841 타원체인 카텍 좌표계에서 WGS84 타원체인 UTM 52Zone 좌표계로 변환하는 내용입니다. 이 두개의 타원체는 서로 다른 타원체를 갖으며 이렇게 서로 다른 타원체를 갖는 좌표계 간의 변환은 다음과 같은 절차를 갖습니다.

  1. 카텍 좌표를 Bessel1841의 경위도 좌표계로 변환
  2. Bessel1841의 경위도 좌표를 WGS84 경위도로 변환
  3. WGS84 경위도를 UTM 52Zone 좌표계로 변환

동일한 타원체를 갖는 좌표계간의 변환에서 한단계(2번)가 추가되었습니다. 위의 세가지 단계에 대해서 하나 하나 코드를 통해 살펴보겠습니다. 먼저 1. 번에 대한 코드는 다음과 같습니다.

String[] projParams_KATECH = {   
    "+proj=tmerc",   
    "+lat_0=38N",   
    "+lon_0=128E",   
    "+ellps=bessel",   
    "+x_0=400000",   
    "+y_0=600000",   
    "+k=0.9999",   
    "+unit=m"  
};   
			        	     
Projection projKatech 
    = ProjectionFactory.fromPROJ4Specification(projParams_KATECH);
			        	
Point2D.Double in = new Point2D.Double(342353, 417704);
Point2D.Double out = new Point2D.Double();
projKatech.inverseTransform(in, out);

다음으로 2. 번에 대한 코드입니다.

Ellipsoid bessel1841 = new Ellipsoid(6377397.155, 1.0 / 299.1528128254262);
Ellipsoid wgs1984 = new Ellipsoid(6378137, 1.0 / 298.257223563);   
Parameters7 params = 
    new Parameters7(-145.907, 505.034, 685.756, -1.162, 2.347, 1.592, 6.342);   
Ellip2Ellipsoid ellip2ellip = new Ellip2Ellipsoid(bessel1841, wgs1984, params);
Values3 src = new Values3(out.y, out.x, 0);
Values3 dst = new Values3();
ellip2ellip.transform(src, dst);

위의 코드를 보면 타원체 간의 변환에 필요한 7개의 파라메터가 사용되고 있음을 알 수 있습니다. 파라메터가 필요없다면 7개의 값을 모두 0으로 지정하면 됩니다. 이제 3. 번에 대한 코드입니다.

String[] projParams_UTM52 = {   
    "+proj=tmerc",   
    "+lat_0=0",   
    "+lon_0=129E",   
    "+ellps=WGS84",   
    "+x_0=500000",   
    "+y_0=0",   
    "+k=0.9996",   
    "+unit=m",
    "+no_defs"
};   
			        	     
Projection projUTM52 
    = ProjectionFactory.fromPROJ4Specification(projParams_UTM52);
			        	
in.x = dst.V2;
in.y = dst.V1;
projUTM52.transform(in, out);

이상으로 서로 다른 좌표계간의 좌표변환에 대한 내용을 마칩니다.

워터코리아(Water Korea) 2014에서

3월 18일 일산 킨텍스(KINTEX)에서 워터코리아 2014가 열렸습니다. 한국상수도협회에서 주관하는 행사이고 국내외 물산업 분야에서 종사하고 있는 정부, 관공서, 산업계, 학계, 연구소 등의 모든 종사자들이 참여하는 국제적인 물산업 축제의 장입니다.

사용자 삽입 이미지

이날 (주)지오서비스의 웹 GIS 솔루션인 FingerEyes-Xr과 GeoService-Xr을 이용한 공간정보정책지원시스템이 전시되었습니다. 이 시스템은 GIS와 상수도 관련 정보를 매우 효과적으로 활용 및 분석할 수 있는 전문가 시스템입니다.

사용자 삽입 이미지

Oracle Spatial Geometry Type 및 Spatial Table 생성

[출처] 이 글은 오라클에서 제공하는 Oracle Spatial User.s Guide를 참고로 하였습니다.

오라클에서 지원하는 지오메트리는 총 9가지로 Point, LineString, Polygon, Arc Line Stringm Arc Polygon, Compound Polygon, Compound Line String, Circle, Rectangle로 다음과 같습니다.

사용자 삽입 이미지

이 중 Point, Line String, Polygon은 GIS의 Simple Feature 정의를 따르는 것은 Point, Line String, Polygon입니다. 오라클에서는 Line String을 구성하는 선분의 교차를 허용합니다.

공간 테이블을 생성하는 SQL 문은 다음과 같습니다. 공간 데이터는 도형과 속성으로 구성되는 피쳐(Feature) 이므로 예를들어 테이블명이 cola_markets라고 할때 도형에 대한 필드명은 shape이며 SDO_GEOMETRY 타입으로 정의되며 속성은 예를 들어 mkt_id와 name이라는 필드명으로써 각각 NUMBER와 VARCHAR2 타입으로 정의한다고 할때 다음과 같습니다.

CREATE TABLE cola_markets 
(
  mkt_id NUMBER PRIMARY KEY,
  name VARCHAR2(32),
  shape SDO_GEOMETRY
);

이렇게 생성된 테이블에 하나의 레코드를 추가하는 SQL 문의 예는 다음과 같습니다.

INSERT INTO cola_markets VALUES
(
  1,
  'cola_a',
  SDO_GEOMETRY(
    2003, -- 2차원의 폴리곤
    NULL,
    NULL,
    SDO_ELEM_INFO_ARRAY(1,1003,3), -- 1개의 사각형(1003 = exterior)
    SDO_ORDINATE_ARRAY(1,1, 5,7) -- 사각형을 정의하기 위해 오직 2개의 포인트 필요
  )
);

다음은 또 다른 레코드를 추가하는 예로, 도형의 타입이 Polygon인 SQL 문의 예입니다.

INSERT INTO cola_markets VALUES
(
  3,
  'cola_c',
  SDO_GEOMETRY(
    2003,
    NULL,
    NULL,
    SDO_ELEM_INFO_ARRAY(1,1003,1), -- 1개의 폴리곤
    SDO_ORDINATE_ARRAY(3,3, 6,3, 6,5, 4,5, 3,3)
  )
);

다음은 또 다른 레코드를 추가하는 예로써, 도형의 타입이 원(Circle)인 SQL 문의 예입니다.

INSERT INTO cola_markets VALUES
(
  4,
  'cola_d',
  SDO_GEOMETRY(
     2003,
     NULL,
     NULL,
     SDO_ELEM_INFO_ARRAY(1,1003,4), -- 1개의 원
     SDO_ORDINATE_ARRAY(8,7, 10,9, 8,11) -- 1개의 원은 최소 3개의 점으로 정의됨
  )
);

공간 테이블은 R-Tree 등과 같은 공간 인덱싱이 적용되어져야 빠른 공간 검색 등이 가능합니다. 아래는 앞서 생성한 공간 테이블에 대해서 공간 인덱싱을 생성해 주는 SQL 문입니다.

INSERT INTO user_sdo_geom_metadata
(
  TABLE_NAME,
  COLUMN_NAME,
  DIMINFO,
  SRID
)
VALUES 
(
  'cola_markets',
  'shape',
  SDO_DIM_ARRAY -- 20 X 20 그리드
  ( 
    SDO_DIM_ELEMENT('X', 0, 20, 0.005),
    SDO_DIM_ELEMENT('Y', 0, 20, 0.005)
  ),
  NULL -- SRID
);

CREATE INDEX cola_spatial_idx
ON cola_markets(shape) INDEXTYPE IS MDSYS.SPATIAL_INDEX;

이제 이렇게 공간 인덱싱이 적용된 공간 테이블에 대해서 다양한 공간 연산을 수행할 수 있으며 아래의 예와 같은 SQL문을 통해 공간 연산을 수행할 수 있습니다.

-- 2개의 도형과 교차하는 도형 얻기
SELECT SDO_GEOM.SDO_INTERSECTION(c_a.shape, c_c.shape, 0.005)
FROM cola_markets c_a, cola_markets c_c
WHERE c_a.name = 'cola_a' AND c_c.name = 'cola_c';

-- 2개의 도형(지오메트리, Geometry)에 대한 공간관계 연산의 결과 얻기
SELECT SDO_GEOM.RELATE(c_b.shape, 'anyinteract', c_d.shape, 0.005)
FROM cola_markets c_b, cola_markets c_d
WHERE c_b.name = 'cola_b' AND c_d.name = 'cola_d';

-- 해당 공간 테이블의 전체 면적 얻기
SELECT name, SDO_GEOM.SDO_AREA(shape, 0.005) FROM cola_markets;

-- 특정 도형에 대한 면적 얻기
SELECT c.name, SDO_GEOM.SDO_AREA(c.shape, 0.005) FROM cola_markets c
WHERE c.name = 'cola_a';

-- 2개의 도형 간의 거리 얻기
SELECT SDO_GEOM.SDO_DISTANCE(c_b.shape, c_d.shape, 0.005)
FROM cola_markets c_b, cola_markets c_d
WHERE c_b.name = 'cola_b' AND c_d.name = 'cola_d';

-- 특정 지오메트리가 옳바른가?
SELECT c.name, SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(c.shape, 0.005)
FROM cola_markets c WHERE c.name = 'cola_c';