[Java] 파일 복사

fileName이 복사할 대상 파일이고 newFileName이 복사되어 새롭게 생성될 파일명입니다. 근데 좀 살펴볼게… 실제 데이터를 복사(전송) 시키는 12번 코드의 transfer 함수는 실제로 전송된 바이트 수를 반환합니다. 전송하고자 하는 바이트수와 실제로 전송된 바이트 수 사이에 차이가 있을 수 있다는 건데.. 이 부분에 대한 고민을 좀 더 해봐야할 코드입니다.

try {
    File inFile = new File(fileName);
    FileInputStream inputStream = new FileInputStream(inFile);
    
    File outFile = new File(newFileName);
    FileOutputStream outputStream = new FileOutputStream(outFile);
   
    FileChannel fcin = inputStream.getChannel();
    FileChannel fcout = outputStream.getChannel();
   
    long size = fcin.size();    
    fcin.transferTo(0, size, fcout);
   
    fcout.close();
    fcin.close();
    
    outputStream.close();
    inputStream.close();
} catch (Exception e) {
    e.printStackTrace();
}   

[Java] 특정 폴더에서 원하는 확장자를 가지는 파일 목록 구하기

원하는 폴더 안에.. 특정한 확장자를 가지는 파일 목록을 얻어야 할때가 있습니다. 예를 들어서 D:/TEMP라는 폴더안에 확장자가 SHP인 파일의 목록을 배열 형태로 반환하도록 하는 경우이지요. 이때 사용할만한 함수입니다.

private Vector getFileNames(String targetDirName, String fileExt) {
    Vector fileNames = new Vector();
    File dir = new File(targetDirName);
    fileExt = fileExt.toLowerCase();
  
    if(dir.isDirectory()) {
        String dirName = dir.getPath();
        String[] filenames = dir.list(null);
        int cntFiles = filenames.length;
       
        for(int iFile=0; iFile            String filename = filenames[iFile];
            String fullFileName = dirName + "/" + filename;
            File file = new File(fullFileName);
 
            boolean isDirectory = file.isDirectory();
            if(!isDirectory && filename.toLowerCase().endsWith(fileExt)) {
                fileNames.add(fullFileName);
            }
        }
    }

    return fileNames;
 }

제가 이 함수가 필요했던 이유는.. 특정 폴더에 존재하는 수백개의 항공영상이나 수백개의 SHP 파일을 한꺼번에 레이어로 추가하고자 하는 필요 때문이였습니다.

아래의 코드는 안드로이드 기반의 GIS 엔진인 블랙포인트에서 위의 함수를 사용해 25cm 해상도의 192개의 항공영상(GEOTIFF 기준으로 40GB 이상)과 일정한 격자로 나눈  SHP 파일 185개(전체 용량 85MB)를 올리는 코드예입니다.

LayerManager layerMan = map.getLayerManager();
  
String ess = Environment.getExternalStorageState();   
String sdCardPath = null;   
if(ess.equals(Environment.MEDIA_MOUNTED)) {   
    sdCardPath = Environment.getExternalStorageDirectory().getAbsolutePath();
    String rootDir = sdCardPath + "/mapdata/yp";
    
    // 항공사진 레이어 추가  
    Vector xrrFiles = getFileNames(rootDir +"/XrR", "xrr");
    for(int i=0; i        ILayer layer = new TileImageLayer("xrr_" + i, xrrFiles.get(i), false);
        layerMan.addLayer(layer);
    }

    // 수치지도 레이어 추가
    Vector cassFiles = getFileNames(rootDir + "/CBND", "shp");
    int cntCbndLyr = cassFiles.size();
    for(int i=0; i        ILayer layer = new ShapeLayer("cbnd_" + i, cassFiles.get(i));
        layerMan.addLayer(layer);
       
        ShapeLayerLabel roadLbl = (ShapeLayerLabel)shpLyr.getLabel();
        roadLbl.setFieldName("JIBUN");
        roadLbl.setEnable(true);
        roadLbl.getFontSymbol().setTextSize(11);
        SimpleDrawShapeTheme roadTheme = (SimpleDrawShapeTheme)shpLyr.getTheme();
        roadTheme.getFillSymbol().setHollow(true);
        roadLbl.getFontSymbol().setTextColor(Color.GREEN);
        roadTheme.getStrokeSymbol().setColor(Color.YELLOW);
}

아래는 위의 코드에 반영된 시스템에 대한 실행 화면입니다. 클릭하면 원본 크기로 볼 수 있습니다.

[Java] URL을 통한 Binary 데이터 받기

자바에서 서버가 제공하는 바이너리 데이터를 받아 저장해 주는 코드입니다. 필요할 때 찾아 보기 쉽도록 정리해 봅니다.

URL url;
try {
    url = new URL("http://somewhere/binary.zip");
  
    URLConnection connection = url.openConnection();
    int contentLength = connection.getContentLength();
    if (contentLength > 0) {
        InputStream raw = connection.getInputStream();
        InputStream in = new BufferedInputStream(raw);
        byte[] data = new byte[contentLength];
        int bytesRead = 0;
        int offset = 0;
        while (offset < contentLength) {
            bytesRead = in.read(data, offset, data.length - offset);
            if (bytesRead == -1) break;
            offset += bytesRead;
        }
        in.close();
  
        if (offset == contentLength) {
            // 바이너리 데이터 준비 완료 !
            return true;
        }
    }
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

위의 코드는 사용해 보니 문제가 있습니다. 이유는 6번 코드를 통해 서버로부터 받은 데이터의 크기가 항상 옳바르지 않다는 것입니다. 예를 들어 데이터가 클 경우.. -1이 반환되기도 합니다. 이러한 이유로 위의 코드는 사용하면 않되고.. 아래의 코드를 사용하시기 바랍니다.

private boolean requestUrl(String urlString, ByteBuffer out) {
    URL url;
  
    try {
        url = new URL(urlString);
        URLConnection connection = url.openConnection();
        InputStream raw = connection.getInputStream();
        InputStream in = new BufferedInputStream(raw);
        byte[] chunk = new byte[1024];
        int bytesRead = 0;

        while((bytesRead = in.read(chunk)) != -1) {
            out.put (chunk, 0, bytesRead);
        }
   
        out.flip();
        in.close();
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }  
    
    return true;
}

[Java] 배열로부터 바로 ArrayList 객체 생성하기

자바의 순수한 배열이 있다고 할때.. 이 배열을 다른 객체에 넘기기 위해서 ArrayList 타입으로 변환할 필요가 있는 경우가 있습니다. 이때 배열을 바로 ArrayList 타입으로 변경하는 코드입니다.

String[] filenames = ...;

List filelist = Arrays.asList(filenames);
ArrayList array = new ArrayList(filelist);

잊어 먹지 말자는 의미에서 기록 남깁니다..

[Java] 타원체간의 경위도 좌표계 변환 오픈소스 라이브러리

얼마전에 가벼운 좌표변환 오픈소스 라이브러리를 소개한 글(가벼운 좌표변환 오픈소스 라이브러리)을 올렸습니다. 상당히 가볍고 매우 다양한 좌표계 투영이 가능한 라이브러리이지만 서로 다른 타원체 간의 좌표 변환에 있어서 제약이 있는 라이브러리였습니다.

이 글은 그에 대한 해결책으로 서로 다른 타원체 간의 경위도 좌표계 간의 변환을 지원합니다. 먼저 소개해 드린 라이브러리와 이 글에서 소개해 드릴 라이브러리를 조합하면 상당한 정확한 좌표 변환 성과를 얻으실 수 있으리라 확신합니다.

먼저 이 라이브러리를 구성하고 있는 클래스(총 4개)들의 관계도를 살펴보면 다음과 같습니다.

사용자 삽입 이미지
보시면.. Ellip2Ellipsoid라는 클래스만이 나머지 클래스와 관계를 맺고 있고 나머지는 독립적입니다. 관계를 맺고 있지 않은 클래스를 먼저 살펴보는 것이 순서이므로.. 순서대로 하나 하나 살펴보면.. 먼저 Ellipsoid는 타원체를 나타냅니다. 장반경과 편평도로 타원체 하나를 정의할 수 있습니다. 그리고 Parameters7은 타원체간의 경위도 좌표 변환을 위한 변환 계수로써 7 Parameters를 의미합니다. Vaues3는 단순히 3개의 값을 담고 있는 클래스로써 경위도값과 높이값을 담는데 사용합니다. 이 클래스는 타원체간의 경위도 좌표 변환의 입력값고 결과값의 타입으로 사용합니다. 끝으로 이 세 클래스와 유일하게 관계를 맺고 있는 Ellip2Ellipsoid는 2개의 상이한 타원체 간의 경위도 좌표를 7개의 변환 계수를 사용해 변환해 주는 주요 클래스입니다.

이제 이 클래스들을 이용하여 실제로 Bessel1841 타원체와 WGS84 타원체 간의 경위도 좌표 변환의 코드를 예로 살펴보겠습니다.

Ellipsoid bessel1841 = new Ellipsoid(6377397.155, 1.0 / 299.152813);
Ellipsoid wgs1984 = new Ellipsoid(6378137, 1.0 / 298.257223563);
Parameters7 params = new Parameters7(
    -115.8, 474.99, 674.11, 
    -1.16, 2.31, 1.63, 
    6.43
);
  
Ellip2Ellipsoid transform = new Ellip2Ellipsoid(bessel1841, wgs1984, params);
  
Values3 src = new Values3(38, 128, 0);
Values3 dst = new Values3();
  
System.out.println("bessel lat/lng -> wgs84 lat/lng");
transform.transfom(src, dst);
System.out.println(src + " -> " + dst + "\n");

System.out.println("wgs84 lat/lng -> bessel lat/lng");
transform.reverseTransform(src, dst);
System.out.println(src + " -> " + dst);

1번와 2번 코드에 앞서 언급했던 2개의 타원체를 정의하고 있습니다. 타원체 정의는 장반경과 편평도값을 통해 가능합니다. 3번 코드는 타원체 간의 변환을 위한 변환계수로써 X, Y, Z의 3개 축에 대한 이동량 그리고 또 3개의 축에 대한 회전량 끝으로 축척차값입니다. 타원체 간의 경위도 좌표 변환은 단번에 이루어지는 것이 아니라 중간 단계로 지심좌표계라는 X, Y, Z축 좌표계로 변환하게 되는데 다시 지심좌표계를 또 다른 타원체로 변환하기 위해 지심좌표계 자체를 3축에 대해 이동하고 회전하며 크기를 조절하는 과정에서 이 7개의 변환 매개변수가 사용됩니다. 9번 코드를 통해 이렇게 생성한 2개의 타원체와 변환 매개변수로써 Ellip2Ellipsoid를 생성합니다. 그리고 11번 코드부터는 실제 각 타원체간의 경위도 좌표계의 변환입니다. 결과는 아래와 같습니다.

사용자 삽입 이미지
만약 다른 프로그램 등을 통해 좌표변환을 수행했을때 위의 결과와 차이를 보인다면 변환 매개변수값으로 다른 값을 사용했기 때문입니다. 즉 위의 코드에서 3번 코드의 Parameters7 클래스의 생성시 사용한 인자값들에 해당합니다. 위의 3번 코드에서 사용한 변환 매개변수를 현재 한국에서 사용하도록 권장하고 있는 매개변수로써 대다수의 좌표변환 툴에서 사용하고 있는 매개변수입니다. 끝으로 본 오픈소스에 대한 다운로드는 아래의 링크를 통해 받으시기 바랍니다.

끝으로 궁금하신 점은 댓글을 통해 남기시면 최대한 답변해 드리겠습니다. 또한 이 오픈소스 라이브러리는 지오서비스에서 개발했으며 LGPL 라이센스를 따릅니다.