'분류 전체보기'에 해당되는 글 72건

  1. 2011.08.02 gdal2tiles options 4
  2. 2011.08.01 gdal_vrtmerge 설치 2
  3. 2011.07.27 gdalwarp(nodata)
  4. 2011.07.27 gdal utility
  5. 2011.07.25 hillshade 지도 생성방법 모음
  6. 2011.06.27 safari 이벤트 입력 설명
  7. 2011.02.16 gdal 유틸리티(gdalinfo)
  8. 2011.02.09 GRASS 사용법
  9. 2010.12.28 Cross Document Messaging
  10. 2010.12.17 OLAY 토탈이펙트 1

gdal2tiles 삽질

원래는 tif파일하나가 400MB정도 나가서 도저히 Merge를 할 수가 없어서 이 방법, 저 방법 찾다가 우연찮게 발견한 것이 gdal_vrtmerge라는 툴이었다(http://code.google.com/p/maphew/ 가면 Download에 gdal_extra라는 파일에 끼어있음). 공식 GDAL 유틸리티는 아니지만 삽질을 통해 설치하고 gdalwarp의 -tr 옵션을 주어서 PixelSize까지 맞춰서 수 많은 tif파일들을 Merge시킨 vrt 파일을 만들었다. 그냥 gdal_merge를 통해 하나의 통판으로 만들면 용량이 너무 커서 시간도 오래걸리고 쪽날꺼 같아 gdal_vrtmerge를 사용 하였다(참고: Open Source GIS를 이용한 고해상도 영상의 Tile Map Service 시스템 구축에 관한 연구-정명훈).

 

근데 제길슨

gdal2tiles.py: error: Processing of several input files is not supported.

Please first use a tool like gdal_vrtmerge.py or gdal_merge.py on the files:

위와 같은 에러가 뜨는게 아닌가!!

구글링을 하다가 http://www.mail-archive.com/debian-bugs-closed@lists.debian.org/msg280389.html 이것을 찾았고

답변은.. but current (>= 1.6) has a different syntax. Sorry for the noise...

시발놈…

 

그래서 혹시나 나중에 삽질하는 사람을 위해서 gdal2tiles 옵션을 정리하고자 한다.

gdal2tiles 실행 방법

gdal2tiles.py는 FWTools와 OSGeo4w 쉘(shell)을 통하여 실행할 수 있다. 하지만 FWTools에서는 GDAL의 버전이 잘 안맞는 경우가 있는데 그냥 OSGeo4W shell을 통하여 실행하면 된다. 먼저 gdal1.6 라이브러리를 활성화 시키기 위해 gdal16이라고 치고 커맨드를 실행시키면 OSGeo4W가 gdal16.bat을 실행시키면서 환경변수 등을 설정한다.

그 다음 gdal2tiles 커맨드와 적절한 옵션을 실행시키면 된다. 커맨드를 실행 시킬 때는 gdal2tiles.py 혹은 gdal2tiles 둘 다 같은 효과를 갖는다. 그 이유는 gdal2tiles.bat이 결국 아래와 같이 gdal2tiles.py를 실행시키는 구조이기 때문이다.

@python "%OSGEO4W_ROOT%\apps\gdal-16\bin\gdal2tiles.py" %*


이제 gdal2tiles 커맨드와 함께 아래 옵션 값들을 참조하여 자신에게 맞는 옵션들을 기입하면 된다.

gdal2tiles와 관련된 옵션

옵션명

옵션1

옵션2

Type

설명

OptionChoice

profile

-p

--profile

choice

프로파일

mercator, geodetic, raster

resampling

-r

--resampling

choice

리샘플링 방법 지정

average,
near, bilinear, cubic, cubicspline, lanczos, antialias

SRS

-s

--s_srs

 

원본파일의 Spatial Reference System

 

zoom

-z

--zoom

 

2-5 혹은 10과 같은 식으로 지정

 

resume

-e

--resume

 

복구재개모드. Resume mode. Generate only missing files

 

NODATA

-a

--srcnodata

 

NODATA transparency value to assign to the input data

 

verbose

-v

--verbose

 

Print status messages to stdout

 

KML

-k

--force-kml

 

Generate KML for Google Earth - default for 'geodetic' profile and 'raster' in EPSG:4326. For a dataset with different projection use with caution!")

 

No KML

-n

--no-kml

 

Avoid automatic generation of KML files for EPSG:4326

 

url

-u

--url

 

URL address where the generated tiles are going to be published

 

webviewer

-w

--webviewer

 

Web viewer to generate Google Maps, Yahoo Maps, OpenLayers- default 'all'

 

title

-t

--title

 

Title of the map

 

copyright

-c

--copyright

 

Copyright for the map

 

Google Maps API key

-g

-googlekey

 

Google Maps API key from
http://code.google.com/apis/maps/signup.html

 

Yahoo Application ID

-y

--yahookey

 

Yahoo Application ID from
http://developer.yahoo.com/wsregapp/

 

tileindex

-i

--tileindex

 

Generate tileindex and mapfile for MapServer (WMS)

 

 

 

gdal2tiles 옵션의 기본 값

verbose=False

profile="mercator"

kml=False

url=''

webviewer='all'

copyright=''

resampling='average'

resume=False

googlekey='INSERT_YOUR_KEY_HERE'

yahookey='INSERT_YOUR_YAHOO_APP_ID_HERE'

Posted by 강부자아들
,

gdal_vrtmerge 설치는 그냥 대충 설치한 것이기 때문에 정말 필요한 사람만 따라 하기를 바랍니다. 거의 뺑기를 써서 설치하였기 때문에 다른 방법을 아시는 분은 댓글 부탁 드립니다.

 

gdal_vrtmerge를 실행하였으나

C:\Program Files\gdal_extras>gdal_vrtmerge.py

Traceback (most recent call last):

File "C:\PROGRA~1\FWTOOL~1.7\bin\gdal_vrtmerge.py", line 27, in <module>

import gdal

File "C:\PROGRA~1\FWTOOL~1.7\pymod\gdal.py", line 191, in <module>

import _gdal

ImportError: No module named _gdal

 

위와 같은 오류를 접하게 되었다.

가장 근접한 Q&A로 http://old.nabble.com/unable-to-run-gdal_retile.py-td14222713.html를 찾았다

 

You can not use any other Python with GDAL than that installed together with FWTools.

Check if the other installed Python is not in the PATH and PYTHONPATH. If it is, then there is some conflict probably.

 

 

아마 내 것에는 PYTHONPATH 환경변수에 Mapnik이 잡혀있어서 안 되는 것 같았다. 아니면 gdal2tiles도 안 되는 것을 봐서는 Python 바인딩이 잘못 된 것도 같았다. 이유는 아직 모르겠다.

 

결국 해결책은 그냥 OSGEO4W를 실행하는 것이었다.

다운받은 gdal_extras\bin\gdal_vrtmerge.py를 복사해다가 OSGEO4W의 각종 유틸리티가 있는 C:\OSGeo4W\bin\ 안에 붙여 넣었다.

그리고 gdal_vrtmerge.bat 이라는 bat파일을 만들고 그 안에 다음과 같이 적어주었다.

@python "C:\OSGeo4W\bin\gdal_vrtmerge.py" %*

 

마지막으로 OSGEO4W shell을 실행시키고 gdal16을 타이핑하여 gdal16 라이브러리를 활성화 시키고 gdal_vrtmerge를 실행한 화면이다.

Posted by 강부자아들
,

GDAL(http://www.gdal.org/)에는 gdal과 관련된 많은 유틸리티 툴이 있다. 이 툴들은 다른 라이브러리들과 종속성이 있어 GDAL을 포함하고 있는 FWTools(http://fwtools.maptools.org/)를 설치하는 것을 권장한다. FWTools를 설치한 후 FWTools shell을 실행한다. 도스 커맨드 창이 열리게 되는데 여기에 http://gdal.org/gdal_utilities.html에 있는 유틸리티 이름과 유틸리티의 인자 값들을 넣어 실행시키면 된다. 기본 폴더는 FWTools가 설치된 경로이다.

내가 설치한 경로는 C:\Program Files\FWTools2.4.7 이고, 이 폴더 안에 데이터들을 넣고 실행하면 된다. 이 폴더 안에 데이터를 넣지 않고 실행할 경우 데이터 파일들에 대한 경로를 지정해야 한다. 파일명으로만 인자 값들을 전달하고 싶을 때는 FWTools shell을 실행한 후 데이터 파일로 찾아간 후("cd [디렉토리 명]"을 입력) 파일명만 입력하는 방법도 있다.

 

이번 글에서는 Tiff 이미지에서 특정한 색의 영역을 투명처리하고 싶을 때 어떻게 처리하는 가에 대한 방법을 설명하고자 한다. 사용될 GDAL의 유틸리티는 gdalwarp이다. 이 gdalwarp 유틸리에는 –srcnodata, -dstnodata, -dstalpha와 같은 옵션을 투명처리를 하기 위해 지원한다. 투명처리를 위해서는 –dstalpha 옵션을 통해 생성될 이미지에 알파채널을 생성하게 한다. 그냥 쓰고 싶으면 아래와 같은 최종 결과 command만 넣어주면 된다.

최종 결과 command

gdalwarp -dstalpha -srcnodata 0 -dstnodata 0 input.tif output_alpha.tif

gdalwarp의 인자 값 설명

-srcnodata value [value...]:

Input band들에 대한 nodata masking value설정(밴드 별로 각각 다른 값을 설정 있음. 예를 들어 -srcnodata "125 125 150"). 만약 1 이상의 값이 주어진다면 따옴표 안에 공백으로 분리하여 지정해 준다. 마스크 값들은 컬러 값을 보간 사용되지 않는다. None 이라는 값을 넣으면 원본 데이터 세트의 nodata 세팅 값을 무시하게 한다.

예제:

gdalwarp -srcnodata "125 125 150" -dstnodata "125 125 150" orig-ignore-grey.tif grey-nodata.tif

 

-dstnodata value [value...]:

output band 대한 nodata (밴드 별로 각각 다른 값을 설정 있음. 예를 들어 -dstnodata "125 125 150"). 만약 1 이상의 값이 주어진다면 따옴표 안에 공백으로 분리하여 지정해 준다. 새로운 파일은 설정된 값으로 초기화 것이고, 만약 가능하다면 출력 파일에 nodata 값이 기록될 것이다. New files will be initialized to this value and if possible the nodata value will be recorded in the output file.

-dstalpha:

nodata(unset/transparent) 픽셀을 구별 하기 위해 출력 파일에 alpha 밴드를 생성한다.

 

 

nodata 예제들

GDALWARP WITH TRANSPARENCY : http://schwehr.org/blog/archives/2008-02.html

gdalwarp -dstalpha -srcnodata 255 -dstnodata 0 -s_srs EPSG:32619 -t_srs EPSG:4326 input-32619.tif output-4326.tif

 

만약 geotiff 파일의 헤더에 nodata 태그가 설정되어 있다면, 우리가 아무런 작업을 필요 없이 gdalwarp 유틸리티는 자동으로  nodata 태그를 사용할 것이다. 하지만-srcnodata 인자 값은 nodata 태그 값을 덮어 쓰기 있다. 만약 우리가 여러 장의 영상과 영상 별로 다른 값들을 nodata  다루어야 한다면, 각각의 영상 별로 무시해야 nodata 값들을 정해주어 프로세싱하고, 각각의 파일마다 nodata flag 달아 주어야 한다. 만약 우리가 원래 값을 보존해야 한다고 하면 예제는 아래와 같다:

# for this image we want to ignore black (0)

gdalwarp -srcnodata 0 -dstnodata 0 orig-ignore-black.tif black-nodata.tif

 

# and now we want to ignore white (0)

gdalwarp -srcnodata 255 -dstnodata 255 orig-ignore-white.tif white-nodata.tif

 

# and finally ignore a particular blue-grey (RGB 125 125 150)

gdalwarp -srcnodata "125 125 150" -dstnodata "125 125 150" orig-ignore-grey.tif grey-nodata.tif

 

# now we can mosaic them all and not worry about nodata parameters

gdalwarp -dstnodata 0 black-nodata.tif grey-nodata.tif white-nodata.tif final-mosaic.tif

 

http://osgeo-org.1803224.n2.nabble.com/transparent-Geotiff-td2022648.html

위 링크에 질문한 사람은 하얀색 부분을 투명 처리 하고 싶다고 하였다.

gdalwarp -dstalpha -srcnodata 255 -dstnodata 0 de_poel.tif de_poel2.tif 

 

 

http://trac.osgeo.org/gdal/wiki/UserDocs/RasterProcTutorial

d) gdalwarp can also be cause to treat particular values as nodata and to 

   produce alpha values in the output.  In this example we cause oceans to

   be treated as transparent, and generate alpha in the output. 

 

   gdalwarp -wo SOURCE_EXTRA=125 -srcnodata "11 10 50" -dstalpha

             -t_srs '+proj=ortho +datum=WGS84' 

             geoworld.tif ortho.tif

 

   Hmm, it seems the water isn't quite as uniformly blue as I hoped.  It

   must have lived as a lossily compressed image at one point!

 

삽질

아래와 같이 실행한 결과 배경이 검정색으로 나타났다. 왜 그런지 정확히 모르겠지만 아마 –dstnodata를 지정하지 않아서 인 것 같다.

gdalwarp -srcnodata 0 -dstalpha input.tif output.tif

 

Reference

GDALWARP WITH TRANSPARENCY : http://schwehr.org/blog/archives/2008-02.html

OSGEO Q&A : http://osgeo-org.1803224.n2.nabble.com/transparent-Geotiff-td2022648.html

RasterProcTutorial : http://trac.osgeo.org/gdal/wiki/UserDocs/RasterProcTutorial

 

Posted by 강부자아들
,

gdal utility

카테고리 없음 2011. 7. 27. 11:37

http://www.gdal.org/ 에는 gdal과 관련된 많은 유틸리티 툴이 있다. 이 툴들은 다른 라이브러리들과 종속성이 있어 Gdal을 포함하고 있는 FWTools(http://fwtools.maptools.org/)를 설치하는 것을 권장한다. FWTools를 설치한 후 FWTools shell을 실행한다. 도스 커맨드 창이 열리게 되는데 여기에 http://gdal.org/gdal_utilities.html에 있는 유틸리티 이름과 유틸리티의 인자 값들을 넣어 실행시키면 된다. 기본 폴더는 FWTools가 설치된 경로이다. 내가 설치한 경로는 C:\Program Files\FWTools2.4.7 이고, 이 폴더 안에 데이터들을 넣고 실행하면 된다.

 

gdal에서는 gdal 유틸리티들에 공통으로 사용할 수 있는 일반 옵션(general option) 값들이 있는데 그 값들은 아래와 같다.

General Command Line Switches

All GDAL command line utility programs support the following "general" options.

--version

GDAL 버전을 표시해 주고 종료한다.

--formats

현재 버전의 GDAL 빌드에서 지원하는 모든 종류의 raster 포맷(read-only read-write)들을 나열한다. 포맷 지원은 다음과 같은 지시어들을 따른다: 'ro' 읽기전용(read-only) 드라이버; 'rw' 읽기, 쓰기 (. CreateCopy지원); 'rw+' 읽기, 쓰기, 업데이트(. Create 지원). 'v' virtual IO (/vsimem, /vsigzip, /vsizip) 지원하는 포맷에 달려있다. 주의: gdalwarp 출력으로 유효한 포맷은 CreateCopy() 매쏘드를 지원하는 포맷이 아니라 Create() 매쏘드(rw+) 지원하는 포맷이다.

--format format

단일 포맷 드라이버에 관련된 정보를 열거한다. format --formats 리스트에 있는 short name 값이다. 예를 들어 GTiff 같은 값이다.

--optfile file

명명된 옵션 파일을 읽고 커맨드라인 옵션리스트를 옵션 파일로 대체해 준다. 옵션파일에서 #으로 시작하는 줄은 무시하고 처리된다(주석처리). 여러 단어로 띄워쓰기 인자 (Multi-word argument) 쌍따음표 안에 넣어 전달한다.

--config key value

설정 값을 기본 환경 변수로 설정하는 것과 대조적으로, --config 옵션에 주어진 key 들은 value (configuration keyword)으로 설정한다. 몇몇의 공통적으로 사용되는 설정 값들은 GDAL_CACHEMAX (메가 바이트 단위로 내부적으로 사용될 캐쉬 메모리의 ) GDAL_DATA (GDAL "data" 경로명)이다. 개개의 드라이버들은 각자 다른 옵션 설정 값들에 의해 영향을 받을 것이다.

--debug value

디버그 메시지를 표시할지 할지 지정한다ON이라고 지정하면 디버그 메시지 기능을 활성화 시키고, OFF라고 주면 모든 디버그 메시지를 비활성화 시킨다. 다른 인자 값은 debug prefix code인데 이것을 지정하면 코드에 맞는 디버그 메시지만을 표시해준다.

--help-general

일반적임 GDAL 커맨드 라인과 관련된 옵션에 대한 간략하고 유용한 정보를 전달해 준다.

 

Posted by 강부자아들
,
Posted by 강부자아들
,

 

입력 타입 요약
Bubble Touch 터치하고 멈춤. 클릭할 수 있는 요소의 팝업 인포 윈도우를 연다.
Flick Touch 터치하고 훑기. 페이지 스크롤.
Flick, Two-Finger Gesture 두 손가락으로 터치하고 훑기. 스크롤 가능한 요소들을 스크롤
Pinch Gesture 두 손가락을 움직여 확대 축소
Tap Touch 가볍게 한 번 두들김. 선택
Tap, Double Touch 두 번 두들김. 확대
Posted by 강부자아들
,

gdalinfo

레스터 데이터 셋에 대한 정보들을 보여준다.
 

문법

 
gdalinfo [--help-general] [-mm] [-stats] [-hist] [-nogcp] [-nomd]
         [-noct] [-checksum] [-mdd domain]* datasetname

설명

gdalinfo 유틸리티 프로그램은 GDAL이 지원하는 레스터 데이터 셋에 대한 다양한 종류의 정보들을 보여준다.
-mm
데이터 셋에 각각의 band 값에 있는 실제 최소/최대 값에 대한 계산을 하여 보여주게 한다.
-stats
이미지에 대한 통계 값들을 읽고 보여준다. 이미지에 통계 값들이 있지 않으면 계산과정으로 넘어가계한다.
-hist
모든 band에 대한 히스토그램을 보고하게 한다.
-nogcp
GCP(ground control point) 리스트를 프린트하지 않게 한다. 만약 수 천 개의 GCP를 가지고 있는 L1B AVHRR 혹은 HDF4 MODIS와 같이 데이터 셋이 많은 양의 GCP들을 가지고 있다면 유용할 것이다.
-nomd
메타데이터를 프린트하지 않게 한다. 몇몇 데이터 셋은 많은 양의 메타데이터 문자열을 가지고 있다.
-noct
컬러 테이블이 프린팅 되지 않게 한다.
-checksum
데이터 셋의 각각의 밴드에 대한 checksum을 계산하게 해준다.
-mdd domain
지정된 도메인에 대한 메타데이터를 프린팅하게 해준다.

만약 있는 값이라면 gdalinfo는 다음과 같은 것들을 보고할 것이다.:

  • 파일에 접근하기 위해 사용된 포맷 드라이버
  • 레스터의 사이즈(픽셀들과 라인들).
  • 파일의 coordinate system (OGC WKT형식).
  • The geotransform associated with the file (rotational coefficients are currently not reported).
  • 지오레퍼런스된 코너들의 좌표와 geotransform에 의한 위경도 좌표(GCP들이 아님).
  • Ground control points.
  • File wide (including subdatasets) metadata.
  • Band의 데이터 타입들
  • Band color interpretation
  • Band 블럭 사이즈.
  • Band 설명.
  • Band 최소/최대 값(내부적으로 알 수 있고 계산 가능한 값)
  • Band checksum (만약에 옵션에 주어졌다면 계산됨).
  • Band NODATA 값.
  • Band overview resolutions available.
  • Band unit type (예를 들어 고도 값 밴드를 위한 "meters" 나 "feet").
  • Band pseudo-color table

예제

 
gdalinfo ~/openev/utm.tif 
Driver: GTiff/GeoTIFF
Size is 512, 512
Coordinate System is:
PROJCS["NAD27 / UTM zone 11N",
    GEOGCS["NAD27",
        DATUM["North_American_Datum_1927",
            SPHEROID["Clarke 1866",6378206.4,294.978698213901]],
        PRIMEM["Greenwich",0],
        UNIT["degree",0.0174532925199433]],
    PROJECTION["Transverse_Mercator"],
    PARAMETER["latitude_of_origin",0],
    PARAMETER["central_meridian",-117],
    PARAMETER["scale_factor",0.9996],
    PARAMETER["false_easting",500000],
    PARAMETER["false_northing",0],
    UNIT["metre",1]]
Origin = (440720.000000,3751320.000000)
Pixel Size = (60.000000,-60.000000)
Corner Coordinates:
Upper Left  (  440720.000, 3751320.000) (117d38'28.21"W, 33d54'8.47"N)
Lower Left  (  440720.000, 3720600.000) (117d38'20.79"W, 33d37'31.04"N)
Upper Right (  471440.000, 3751320.000) (117d18'32.07"W, 33d54'13.08"N)
Lower Right (  471440.000, 3720600.000) (117d18'28.50"W, 33d37'35.61"N)
Center      (  456080.000, 3735960.000) (117d28'27.39"W, 33d45'52.46"N)
Band 1 Block=512x16 Type=Byte, ColorInterp=Gray

Posted by 강부자아들
,

GRASS project는 어떻게 구성되는가

GRASS 데이터는 3단계 구조로 저장된다. database, location 그리고 mapset이다. 이것들은 사용자의 컴퓨터에서 서로를 포함하는 일련의 디렉터리 형식을 가진다. 이 세가지 모두 반드시 있어야 하면 GRASS 시작시 설정되어야 한다.

Database

모든 GIS 데이터들이 저장되어 있는 데이터베이스

e.g. ~/grassdata/

Location

location 은 영역을 이루고 있는 GRASS 프로젝트이고, 투영법(투영법이 없을 수도 있음)과 같은 투영법으로 mapset을 그룹핑하고 있다. location은 GRASS database.의 하위 디렉터리에 존재한다.

e.g. world_lat_lon, utm_zone_59, or west_coast

location 은 하나 혹은 여러 개의 mapsets을 포함한다.

The Mapset

mapset 지도를 포함한다. location의 하위 디렉터리에 있으면 개념적으로 location에 여러 개의 mapset들이 있으면 그것들은 다른 사용자들을 위해 할당 되었을 수도 있다(각각의 사용자는 하나 혹은 여러개의 mapset을 소유하여 다른 사용자들이 그들의 mapset을 수정하지 못하게 한다.). 또한 project (location)를 여러 개의 하위 영역이나 하위 프로젝트로 나누기 위해 구성될 수도 있다. 구성시 특별한 제한 조건은 없다.

location에는 항상 PERMANENT라는 mapset이 존재하는데 모든 다른 mapset에서 같은 위치에 대해 데이터를 읽을 때 사용될 수 있다. 다른 mapset에서 map을 읽기 위한 읽기권한은 'g.mapsets' 커맨드를 통해서나 "@" 심볼을 mapset 이름에 붙임(e.g.map@othermapset)으로써 관리된다.

Posted by 강부자아들
,

Cross Document Messageing

Cross Document Messaging은 iframe, 탭, 윈도우 간의 안전한 cross-origin 통신을 가능하게 해준다. 또한 postMessage API를 메세지를 주고받기 위한 표준화된 방법으로서 제공하고 있다. 아래 예제에서 보여줄 것과 같이 postMessage API를 통해서 메세지를 주고 받는 것은 매우 쉽다.

    chatFrame.contentWindow.postMessage("Hello, world", "http://www.example.com");

메세지를 수신하기 위해서는 페이지에 이벤트 핸들러만 추가시켜 주면 된다. 이벤트 핸들러를 통해 메세지가 도착했을 때, origin을 확인할 수 있고 메세지로 무엇을 할지 말지 결정할 수 있다. 아래는 임으로 정의한 messageHandler라는 함수를 이용하여 메세지를 처리하는 과정을 나타내고 있다.

      window.addEventListener("message",messageHandler,true);
      function messageHandler(e) {
        switch(e.origin) {
          case "friend.example.com":
          // 메세지 처리
          processMessage(e.data);
          break;
        default:
          // 메세지의 origin을 인식할 수 없습니다.
          // 메세지 무시
        }
      }

메세지 이벤트는 dataorigin속성을 가지고 있는 DOM 이벤트이다. data속성은 메세지를 주는 곳이 보내준 실제 메세지이고 origin은 메세지를 보낸 곳이다.origin속성을 사용하여 신뢰할 수 없는 사이트에서 오는 메세지를 차단할 수 있다. 즉, 신뢰할 수 있는 목록의 사이트들만 쉽게 확인할 수 있는 것이다.

아래 그림에서 보는 것과 같이 http://chat.example.net에서 호스팅 되는 채팅위젯 iframe과 http://portal.example.com에서 호스팅되고 있는 채팅위젯 iframe을 포함하고 있는 부모 HTML 페이지가 postMessage API를 통해 통신하는 것을 볼 수 있다. (두 사이트는 .net과.com으로 다른 origin이다.)

그림1

위 예에서 채팅위젯은 iframe에 들어가 있다. 따라서 채팅위젯은 부모 페이지에는 직접적인 접근을 갖지는 못한다. 채팅위젯이 메세지를 받았을 때, 사용자에게 새로운 메세지를 받았다는 것을 메인 페이지에 알리기 위해서 postMessage를 사용할 수 있다. 이와 비슷하게, 메인 페이지(iframe을 보함하는 부모페이지:portal.example.com)도 역시 사용자의 상태정보를 데이터를 채팅위젯 페이지에 보낼 수 있다. 메인 페이지와 위젯 iframe페이지 모두 서로를 신뢰할 수 있는 origin으로 설정하므로써 서로의 메세지를 전달 받을 수 있다.

postMessage가 소개되기 전에, iframe들 간의 통신은 때때로 direct scripting을 통해서 이루어 질 수 있었다. 한 페이지에서 동작하는 스크립트가 다른 문서의 정보를 가져오는 것이다. 이것은 아마도 보안 제약으로 인해 허용되지 않았다. 이러한 직접적인 프로그램적인 접근 대신에, postMessage는 자바스크립트 컨텍스트끼리 메세지를 비동기적으로 주고 받는 방법을 제공한다. postMessage없이 크로스 도메인 통신을 하면, 브라우저가 cross-site 스크립팅 공격을 막기 위해 보안에러를 발생시킨다.

postMessage는 같은 origin을 가진 문서간에 통신을 위해서 사용될 수도 있지만, 브라우저의 same-domain policy에 의해 허용될지 않은 경우의 통신 방법으로 특히 유용하다. 하지만 postMessage가 일관성과 사용하기 쉬운 API이기 때문에 같은 origin을 가지고 있는 경우에도 사용한다. postMessage API는 HTML5 Web Workers와 같이 자바스크립트 컨텍스트 내의 통신이 필요할 때마다 사용한다.

Origin 보안에 대한 이해

HTML5는 origin이라는 개념을 도입하므로써 도메인 보안을 명확히하고 개선하였다. origin은 웹에서 신뢰할 수 있는 연결을 모델링 하기 위해 사용되는 address의 부분집합이다. origin은 scheme, host, port로 구성된다. 예를 들어 https://www.example.comhttp://www.example.com과 다른 origin이다. 왜냐하면 httpshttp라는 다른 scheme을 가지고 있기 때문이다. origin에서 path(경로)는 고려하지 않는다. 따라서 http://www.example.com/index.htmlhttp://www.example.com/page2.html은 path만 다르기 때문에 같은 origin을 갖는 것이다.

HTML5는 origin의 직렬화에 대해 정의하였다. 문자열 형식으로 API와 프로토콜에서 origin을 참조할 수 있다. 이것은 XMLHttpRequest를 이용한 cross-origin HTTP 요청과 WebSocket에서 매우 필수적이다.

Cross-origin 통신은 송신자를 origin으로 확인한다. 이것은 수신자가 신뢰할 수 없는 origin으로부터 오는 메세지나 예상되지 않은 곳에서 오는 메세지를 무시할 수 있게 한다. 더불어 어플리케이션은 이벤트 리스너를 추가하여 선택적으로 메세지를 받아야 한다. 이러한 이유들로 수상한 어플리케이션으로부터 메세지의 간섭 위험이 사라진다.

postMessage를 위한 보안지침은 메세지는 반드시 예상되지 않거나 원치 않는 origin 페이지에 전달되지 말아야 한다는 것이다. 만약 송신자가 postMessage를 호출하는 창이 특정 origin을 가지고 있지 않으면(예를 들어, 사용자가 다른 사이트를 탐색하는 경우) 브라우저는 메세지를 전달하지 않을 것이다.

이와 유사하게 메세지를 받을 때도 송신자의 origin은 메세지에 포함되어 전달된다. 메세지의 origin은 브라우저에 의해 제공되기 때문에 속일 수 없다. 이것은 수신하는 쪽에서 어떤 메세지를 처리하고 어떤 메세지를 무시할지 결정할 수 있게 해준다. 당신은 화이트리스트를 관리하여 신뢰할 수 있는 origin의 문서들의 메세지들만 처리할 수 있다.

postMessage API 사용하기

브라우저 호환성 테스트

postMessage를 호출하기 전에 브라우저의 지원여부를 확인하는 것은 좋은 생각이다. 아래의 예제는 postMessage를 브라우저의 지원하는지 확인하는 방법 중에 하나를 보여주고 있다.

    if(typeof window.postMessage === "undefined") {
        // 브라우저에서 postMessage를 지원하지 않습니다.
    }

메세지 보내기

메세지를 전달하기 위하여, 아래의 예제와 같이 타겟이 되는 window 객체에 postMessage를 호출한다.

    window.postMessage("Hello, world", "portal.example.com");

첫 번째 인자 값은 보내질 값을 나타낸다. 두 번째 인자 값은 목표로 하는 타겟의 origin이다. 메세지를 iframe에 보내기 위해서, postMessage의 contentWindow에 아래 예제와 같이 호출한다.

    document.getElementsByTagName("iframe")[0].contentWindow.postMessage("Hello, world", "chat.example.net");

메세지 이벤트 전달 받기

window 객체의 이벤트 리스너를 통해 스크립트는 아래의 코드와 같이 메세지를 전달 받을 수 있다. 이벤트 리스너 함수에서 메세지를 전달받는 어플리케이션은 메세지은 허용할지 무시할지 결정할 수 있다.

    function checkWhiteList(origin) {
        for(var i=0; i<originWhiteList.length; i++) {
            if(origin === originWhiteList[i]) {
                return true;
            }
        }
        return false;
    }
    
    function messageHandler(e) {
        if(checkWhiteList(e.origin)) {
            processMessage(e.data);
        } else {
            // 알 수 없는 origin으로부터 온 메세지는 무시한다.
        }
    }
    
    window.addEventListener("message", messageHandler, true);

postMessage API를 사용한 어플리케이션 구현

앞서 말한 포탈 어플리케이션에 cross-origin 채팅 위젯을 만든다고 한다고 가정하자. 아래 그림과 같이 Cross Document Messaging을 활용하여 채팅 위젯을 만들 수 있다

2

이 예제를 통해 우리는 포탈 페이지가 서드파티의 위젯을 iframe에 어떻게 넣는지 알았다. 우리의 예제는 http://chat.example.net의 채팅 위젯 하나였다. 포탈 페이지와 위젯은 postMessage를 통하여 통신할 수 있었다. 예제에서 채팅 위젯 iframe은 사용자에게 알림을 전달하기위해 웹 페에지에 타이틀을 깜박 거렸다. 이것은 백그라운에서 이벤트를 받는 어플리케이션들에서 찾아볼 수 있는 일반적인 UI 기술이다. 채팅위젯이 부모 페이지와는 다른 origin에서 서비스되는 iframe에 고립되어 있기 때문에, 부모 페이지의 제목을 바꾸는 것은 보안 위반이다. 대신에 채팅 위젯은 postMessage를 이용하여 부모 페이지에게 알림 메세지를 전달했다.

예제 에서 포탈 페이지는 사용자가 자신의 상태(status)를 바꾸었다고 iframe에게 메세지를 전달한다. postMessage를 이러한 방식으로 사용하므로써 이와 같은 포탈은 채팅위젯과 같이 결합된 페이지 어플리케이션에게 메세지를 전달한다. 물론 목표로 하는 origin은 화이트 리스트를 체크하여 메세지를 선택적으로 받는다. 따라서 메세지가 유출이 사고나 고의적인 의도에 의해 이루어질 수 없다.

자세한 설명을 위해 postMessagePotal.html과 postMessageWidget.html을 생성했다.

포털 페이지 만들기

첫 번째로 다른 origin에서 호스팅 되는 채팅 위젯 iframe을 포탈 페이지에 추가한다.

<iframe id="widget" src="http://chat.example.net:9999/postMessageWidget.html"></iframe>

그 다음은 messageHandler라는 이벤트 리스너를 추가하여 채팅 위젯으로부터 오는 메세지 이벤트를 가져오는 것이다. 아래에서 보는 예제 코드와 같이, 위젯은 포털에게 제목표시를 깜박거리게 할 사용자 알림을 할지를 확인하게 된다. 채팅 위젯으로부터 메세지가 오는지 확인하기 위해 메세지의 origin을 확인한다. 만약 메세지가 http://chat.example.net:9999에서 오지 않는다면 포탈페이지는 메세지를 무시할 것이다.

    var targetOrigin = "http://chat.example.net:9999";
    
    function messageHandler(e) {
        if(e.origin == targetOrigin){
            notify(e.data);
        } else {
            // 다른 도메인에서 온 메세지는 무시한다.
        }
    }

그 다음은 채팅 위젯과 통신할 수 있는 함수를 만드는 것이다. 포털 페이지에 속해 있는 위젯 iframe에게 상태 업데이트를 보내기 위해 postMessage를 사용한다. 실제 라이브 채팅 어플리케이션에서 이것은 사용자 상태(온라인, 부재중 등등)를 알리기 위해 사용될 수 있다.

    function sendString(s) {
        document.getElementById("widget").contentWindow.postMessage(s, targetOrigin);
    }

채팅위젯 페이지 만들기

첫 번째로 포털 페이지로부터 오는 메세지를 전달받기 위해 messageHandler라는 이벤트 리스너를 추가한다. 아래 예제코드와 같이 채팅 위젯은 상태 변화 메세지를 전달받게 된다. 메세지가 포탈페이지로부터 오는지 확인하기 위해 origin을 확인할 것이다. 만약 메세지가 http://portal.example.com:9999로부터 오지 않는다면 위젯페이지는 이 메세지를 무시할 것이다.

    var targetOrigin = "http://portal.example.com:9999";
    
    function messageHandler(e) {
        if(e.origin === "http://portal.example.com:9999") {
            document.getElementById("status").textContent = e.data;
        } else {
            // 다른 origin으로 부터 온 메세지는 무시한다.
        }
    }

그 다음으로 포탈페이지와 통신할 함수를 추가한다. 위젯은 포탈에게 새로운 채팅 메세지가 받아지면 postMessage를 통하여 사용자에게 알림을 할지 아래 예제와 같이 묻는다.

    function sendString(s) {
        window.top.postMessage(s, targetOrigin);
    }

최종 코드

postMessagePortal.html

<!DOCTYPE html>
<html>
    <head>
        <title>Portal [http://portal.example.com:9999]</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styles.css">
        <style>
            iframe {
                height: 400px;
                width: 800px;
            }
        </style>
        <link rel="icon" href="http://apress.com/favicon.ico">
        <script>
            var defaultTitle = "Portal [http://portal.example.com:9999]";
            var notificationTimer = null;
            
            var targetOrigin = "http://chat.example.net:9999";
            
            function messageHandler(e) {
                if(e.origin == targetOrigin){
                    notify(e.data);
                } else {
                    // 다른 도메인에서 온 메세지는 무시한다.
                }
            }
            
            function sendString(s) {
                document.getElementById("widget").contentWindow.postMessage(s, targetOrigin);
            }
            
            function notify(message) {
                stopBlinking();
                blinkTitle(message, defaultTitle);
            }
            
            function stopBlinking() {
                if(notificationTimer !== null){
                    clearTimeout(notificationTimer);
                }
                document.title = defaultTitle;
            }
            
            function blinkTitle(m1, m2){
                  document.title = m1;
                // setTimeout함수의 3번 째 인수부터는 콜백함수의 인자 값으로 들어간다.
                // 여기서는 blickTitle 함수의 인자 값으로 m2, m1을 사용하는 것이다.
                notificationTimer = setTimeout(blinkTitle, 1000, m2, m1);
            }
            
            function sendStatus() {
                var statusText = document.getElementById("statusText").value;
                sendString(statusText);
            }
            
            function loadDemo() {
                document.getElementById("sendButton").addEventListener("click", sendStatus,true);    
                document.getElementById("stopButton").addEventListener("click", stopBlinking,true);
                sendStatus();    
            }
            window.addEventListener("load", loadDemo, true);
            window.addEventListener("message", messageHandler, true);
            
        </script>
    </head>
    <body>
        <h1>Cross-Origin 포탈</h1>
        <p><b>Origin</b>: http://portal.example.com:9999</p>
        Status <input type="text" id="statusText" value="Online">
        <button id="sendButton">Change Status</button>
        <p>이것은 포텔 페이지에 포함되어 있는 위젯 iframe에 상태를 업데이트 하기 위해 postMessage를 이용한다.</p>
        <iframe id="widget" src="http://chat.example.net:9999/postMessageWidget.html"></iframe>
        <p>
            <button id="stopButton">페이지의 타이틀이 깜박거리는 것을 중지</button>
        </p>                
    </body>
</html>

postMessageWidget.html

<!DOCTYPE html>
<html>
    <head>
        <title>Chat Widget</title>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="styles.css">
        <script>
            var targetOrigin = "http://portal.example.com:9999";
            
            function messageHandler(e) {
                if(e.origin === "http://portal.example.com:9999") {
                    document.getElementById("status").textContent = e.data;
                } else {
                    // 다른 origin으로 부터 온 메세지는 무시한다.
                }
            }
            
            function sendString(s) {
                window.top.postMessage(s, targetOrigin);
            }
            
            function loadDemo() {
                document.getElementById("actionButton").addEventListener("click",
                    function () {
                        var messageText = document.getElementById("messageText").value;
                        alert("test");
                        sendString(messageText);
                    }, true);
            }
            
            window.addEventListener("load", loadDemo, true);
            window.addEventListener("message", messageHandler, true);
        </script>
    </head>
    <body>
        <h1>위젯 iframe</h1>
        <p><b>Origin</b>: http://chat.example.net:9999</p>
        <p>포탈에 포함되어 있는 Status를 다음과 같이 설정: <strong id="status"></strong></p>
        <div>
            <input type="text" id="messageText" value="Widget notification.">
            <button id="actionButton">Notification 보내기</button>
        </div>
        <p>이것은 포탈 사이트가 사용자에게 알리지 물을 것이다. 포털 사이트의 제목표시줄은 제목을 깜박거리며 번갈아 가며 반복적으로 보여줄 것이다. 만약 메세지가  http://chat.example.net:9999 이외에서 온다면 포탈 페이지는 이 메세지들을 무시할 것이다.</p>
    </body>
</html>

동작하는 어플리케이션 만들기(서버 세팅)

    1. C:\WINDOWS\system32\drivers\etc 경로로 가서 “hosts” 파일을 메모장으로 엽니다
image
    2. 아래 그림과 같이 127.0.0.1과 chat.example.net, portal.example.com을 적어 넣고 저장합니다image
        3. http://python.org/download/를 방문하여 Python 2.7.1 Python2.7.1 Windows installer를 다운로드 합니다.image
        4. 파이썬이 설치 된 폴더에 책을 보고, 작성하신 postMessagePortal.html과 postMessageWidget.html 파일을 넣습니다
      image
        5. “시작”>”실행”>cmd를 입력하여 커맨드 창을 실행시킨 후 cd C:\Python27을 입력합니다.
      image
        6. 커맨드 창에 python –m SimpleHTTPServer 9999를 입력합니다.

      image

        7. 위의 설정을 다 한후, postMessage API를 지원하는 브라우저의 주소창에 http://portal.example.com:9999/postMessagePortal.html을 입력합니다.
      Posted by 강부자아들
      ,
      Posted by 강부자아들
      ,