마우스의 클릭이 더블클릭으로 오동작할때 컴퓨터

마우스 바닥의 볼트를 풀어 뚜껑을 연다.

볼트를 하나만 풀어도 열리는 게 있고 어떤건 세개 또는 네개를 풀어야 열리는 게 있다.

마우스 왼쪽버튼 밑에 위 그림과 같은 스위치가 있는데 빨간게 칠한 부분에 칼날을 집어넣어서 젖히면 딸깍하면서 한쪽이 열린다. 반대편 같은 자리에도 같은 작업을 해줘야 완전히 열리는데 아마 칼날이 들어갈 공간이 없는게 대부분이다. 그래서 한쪽만 열어둔채 뚜껑을 살살 비틀고 제끼고 누르면서 달래면 꽉 물린 반대편 부위가 너슨해지면서 열리면 성공하는거고 조금만 더 힘을 주면 부러질것 같은데 여전히 열리지 않으면 여는걸 포기하든지 납땜한 부분을 인두로 녹여 스위치전체를 빼내든지 해야된다.

여하튼 ..
뚜껑을 열면 구리판같은게 보이는데 그곳에 wd40 같은 윤활제를 뿌리면 된다. 구리판이 붙었다 떨어졌다 하는 부위에 윤활제가 묻기만 하면 된다.

.끝.



네이버지도와 다음지도 데이타를 파일로 저장하기 여행

여기로 가서 파일을 다운로드한다음 적당한 폴더에 압축을 푼다
먼저 네이버지도부터 시작한다. 

ol3_naver_5179.html 파일을 웹브라우저에 띄워보면 네이버지도가 나타난다.

이 파일의 109라인 뒤에 다음과 같은 코드를 추가하면 지도에 격자가 나타난다.

new ol.layer.Tile({
                          source: new ol.source.TileDebug({
                              projection: 'EPSG:5179',
                              tileGrid: new ol.tilegrid.TileGrid({
                                            extent: extent,
                                            origin: [extent[0], extent[1]],
                                            resolutions: resolutions
                                        })
                              })
                        })


위 코드를 추가한 파일을 첨부했는데 이곳에서는 html파일은 첨부가 안되어서 확장자를 txt로 해서 올린거니까 다운로드 받아서 확장자를 html로 바꾸어서 원래있던 곳에 넣어주면 된다.

군산,대전,세종을 포함하는 격자영역은 (2,6,4)로 나타나는데 이 숫자의 의미는 크롬에서 f12를 눌러서 나타나는 창에서 짐작할 수 있다.

크롬화면 밑에 또는 오른쪽에 나타나는 창에서 network를 선택한뒤 새로고침을 해보면 name 항목에 목록이 나타나는데 그곳을 클릭하면 오른쪽에 그림이 나타난다. 대조를 해보면 (2,6,4)에 해당되는 그림의 네이버주소는 다음과 같다.

http://onetile2.map.naver.net/get/149/0/0/3/6/4/bl_vc_bg/ol_vc_an

(2,6,4)가 3/6/4 로 나타났는데 2가 3으로 바뀐것 말고는 다른 것을 똑같다. 여기서 2는 줌레벨이다. 화면을 확대또는 축소해보면서 앞자리 숫자를 살펴보면 확대가 될수록 그 숫자가 커지는것을 알수있다. 0부터 13까지이고 네이버주소상으로는 1부터 14까지가 된다. 격자숫자를 놓고보면 두번째숫자는 x축을 세번째숫자는 y축을 가르킨다고 보면 된다.

이 그림을 다운로드해서 저장할때 8/6/4.png.tile 로 저장할 것이다. 8과 6은 폴더이름이고 4는 파일이름이 된다.
줌레벨2를 8로 바꿔서 저장하는데 이것은 세계지도의 관점에서 보면 그렇다는 것이다. 네이버의 줌레벨0가 세계지도로 보면 줌레벨 6쯤 된다고 생각하면 된다.

이제 지도파일을 다운로드 받는 파이썬 프로그램을 살펴본다.
#은 주석을 나타냄


#-*- encoding: utf8 -*-
#  지도 데이타를 받아옴 
#
import urllib
import os

# 지도형식 
# 왼쪽 아래 격자 z1,x1,y1
# 오른쪽 위 격자 z2,x2,y2

# 왼쪽 아래가 5,51,45
# 오른쪽 위가 7,54,47
# 인 영역의 지도를 줌레벨 7까지 저장할려면
#
# z1 = 5 , z2 = 7 줌레벨 
# x1 = 51, x2 = 54 x축
# y1 = 45, y2 = 47 y축

# 아래에 나오는 좌표값 6개를 변경
z1 = 5
z2 = 7
x1 = 51
x2 = 54
y1 = 45
y2 = 47

#addr1 = 'http://onetile4.map.naver.net/get/171/0/0/'
#addr2 = '/bl_vc_bg/ol_vc_an'
addr1 = 'https://simg.pstatic.net/onetile/get/196/0/0/'
addr2 = '/bl_vc_bg/ol_vc_an'

def mkdir(z1,z2,x1,x2):
    for z in range(z1,z2):
        if z != z1:
            x1 = x1*2
            x2 = x2*2
        for x in range(x1,x2):
            dirx = str(z) + '/' + str(x)
            if not os.path.isdir(dirx):
                os.makedirs(os.path.join(dirx))

def getMap(z1,z2,x1,x2,y1,y2):
    a = (x2 - x1)*(y2 - y1) #초기값
    total = a*(4**(z2 - z1)-1)/3
    count = 0
    for z in range(z1,z2):
        if z != z1:
            x1 = x1 * 2
            x2 = x2 * 2
            y1 = y1 * 2
            y2 = y2 * 2
        for x in range(x1,x2):
            for y in range(y1,y2):
                filename = '/' + str(x) + '/' + str(y)
                urlzoom = str(z-5) + filename
                downzoom = str(z) + filename
                addr = addr1 + urlzoom + addr2
                count += 1
                print('{}/{}\r'.format(count,total)),
                urllib.urlretrieve(addr,downzoom + '.png.tile')        
z1 = z1 + 6
z2 = z2 + 6 + 1
x2 = x2 + 1
y2 = y2 + 1

mkdir(z1,z2,x1,x2)
getMap(z1,z2,x1,x2,y1,y2)

위 코드를 getnaver.py 로 저장한다.


위그림처럼 12개의 격자에 해당되는 그림을 다운받을려면 왼쪽아래쪽의 격자값을 z1,x1,y1에 넣고 오른쪽위쪽 격자값을 z2,x2,y2에 넣는다. 최종 줌레벨은 7로 한다. 이처럼 원하는 지역과 줌레벨을 위 프로그램에 적는다.

파이썬이 이미 설치되어있다고 치고 s:\ 에 getnaver.py 이 있다고 가정했을때 커맨드창에서 

s:\python getnaver.py 

이렇게 치면 아래와 같은 폴더와 파일이 생긴다.
그림에서는 짤렸는데 13폴더밑에는 16개의 폴더가 있다. 204부터 219까지.


이제 부터는 다음지도 데이타를 받아본다. 네이버와 다를 바가 없다. 그저 주소형식이 약간 다를뿐이다.

ol3_daum_5181.html 을 웹브라우저에서 불러오면 파란화면만 보인다. 그동안에 지도파일의 주소가 바뀐것이다.

다음지도로 간다음 f12를 눌러 지도파일의 주소를 살펴보면 이런식이다.

http://map0.daumcdn.net/map_2d/1810uis/L3/965/1628.png

ol3_daum_5181.html 을 편집기에서 열어서 68라인을 보면 아래와 같이 되어있다.

 return 'http://map' + s + '.daumcdn.net/map_2d/2fso49/L' + z + '/' + y + '/' + x + '.png';

2fso49 를 1810uis 로 바꾼다.

return 'http://map' + s + '.daumcdn.net/map_2d/1810uis/L' + z + '/' + y + '/' + x + '.png';

이렇게 하면 화면에 다음지도가 뜬다. 이제 격자를 넣기 위해서 네이버지도에서 했던것과 거의 유사한 코드를 108라인뒤쪽에 넣는다.
new ol.layer.Tile({
                          source: new ol.source.TileDebug({
                              projection: 'EPSG:5181',
                              tileGrid: new ol.tilegrid.TileGrid({
                                            extent: extent,
                                            origin: [extent[0], extent[1]],
                                            resolutions: resolutions,
                                        })
                              })
                        })


바꾼 전체 코드를 첨부한다. 확장자를 txt에서 html로 바꾼다.

두개의 지도가 격자영역이 약간 다르다. x,y좌표도 다르다. 그래서 다운로드하는 프로그램도 약간 다르다. 물론 주소체계도 약간 다르다

네이버 z/x/y 
다음   Lz/y/x 

프로그램은 다음과 같다.

#-*- encoding: utf8 -*-
#  지도 데이타를 받아옴 
#
import urllib
import os

# 지도형식 
# 왼쪽 아래 격자 z1,x1,y1
# 오른쪽 위 격자 z2,x2,y2

# 왼쪽 아래가 5,12,30
# 오른쪽 위가 5,14,31
# 인 영역의 지도를 줌레벨 7까지 저장할려면
#
# z1 = 5 , z2 = 7 줌레벨 
# x1 = 12, x2 = 14 x축
# y1 = 30, y2 = 31 y축

# 아래에 나오는 값을 변경
z1 = 5
z2 = 7
x1 = 12
x2 = 14
y1 = 30
y2 = 31
# http://map3.daumcdn.net/map_2d/1810uis/L13/2/1.png
addr1 = 'http://map3.daumcdn.net/map_2d/1810uis/L'

def mkdir(z1,z2,x1,x2):
    for z in range(z1,z2):
        if z != z1:
            x1 = x1*2
            x2 = x2*2
        for x in range(x1,x2):
            dirx = str(z) + '/' + str(x)
            if not os.path.isdir(dirx):
                os.makedirs(os.path.join(dirx))

def getMap(z1,z2,x1,x2,y1,y2):
    a = (x2 - x1)*(y2 - y1) #초기값
    total = a*(4**(z2 - z1)-1)/3
    count = 0
    for z in range(z1,z2):
        if z != z1:
            x1 = x1 * 2
            x2 = x2 * 2
            y1 = y1 * 2
            y2 = y2 * 2
        for x in range(x1,x2):
            for y in range(y1,y2):
                urlzoom = str(20-z) + '/' + str(y) + '/' + str(x) + '.png'
                downzoom = str(z) + '/' + str(x) + '/' + str(y)
                addr = addr1 + urlzoom 
                count += 1
                print('{}/{}\r'.format(count,total)),
                urllib.urlretrieve(addr,downzoom + '.png.tile')        
z1 = z1 + 6
z2 = z2 + 6 + 1
x2 = x2 + 1
y2 = y2 + 1

mkdir(z1,z2,x1,x2)
getMap(z1,z2,x1,x2,y1,y2)

네이버와 비교하면 폴더만드는 것은 완전히 같고 주소만드는 방식이 달라 두어줄이 달라졌다. 

[2019.4.26 추가]

특정영역을 하나의 그림파일로 만들기.

네이버지도의 특정영역 

(12,6711,5957) 에서 (12,6720,5964)까지를 다운로드 받은 후 몽땅 합쳐 한장의 png파일로 만들어본다.

각각의 지도는 줌레벨이 같기 때문에 한장의 파일만 만들어진다. 


00. 위 영역을 받아서 임의의 폴더에 넣는다 (여기서는 s:\naver01 )

01. mobac(MObile Atlas Creator) 를 받아온다. 
   
    mobile atlas creator 2.0.1.zip 을 받는다. 최신버전은 앞으로 우리가 해야할 작업에서 약간 오동작한다. 

02. 압축을 푼다. 설치같은거 필요없으나  java가 깔려있지 않으면 동작하지 않는다.
    
03. 아래와 같은 내용의 xml 파일을 하나 만든다. (여기서는 naver.xml)

<?xml version="1.0" encoding="UTF-8"?>
<localTileFiles>
   <name>naver</name>
   <sourceType>DIR_ZOOM_X_Y</sourceType>
   <sourceFolder>s:/naver01</sourceFolder>
   <invertYCoordinate>true</invertYCoordinate>
   <backgroundColor>#000000</backgroundColor>
</localTileFiles>

참고 naver.xml.txt (파일이름은 naver.xml로 바꾼다)

04. 압축을 풀은 폴더밑의 mapsources 폴더밑에 naver.xml을 넣는다.


.05. Mobile Atlas Creator.exe 를 실행한다.

     나타나는 창에서 적당한 이름(여기서는 naver map)를 주고 atlas format에는 [OziExplorer]를 선택한뒤 [확인]을 누른다.



06. Map Source에서 naver를 선택하고 zoom level 에서 18을 체크표시한다. 오른쪽 화면에는 아직 지도가 보이지 않는다.



07. 왼쪽패널에서 탭타이틀을 클릭하면 세부내용이 숨겨지고 제목만 남는다. 딴건 다 숨기고 제일 밑의 [Selection Coordinates..]에서 [Fmt]박스를 열어 [Tile..]을 선택한다.

08. 오른쪽 X 표시가 된 곳중 아무데나 마우스로 클릭하면 왼쪽에 그곳의 타일번호와 줌레벨이 나타난다.

09. 동서남북의 숫자를 바꾼다.

   지도의 영역이 아래와 같은데 이것을 다음과 같이 적는다.

   [12,6711,5957] -> [12,6720,5964]

   네이버 줌레벨 12가 여기서는 z18이다. 

   W : 6711 / z18
   E : 6720 / z18
   S : 2의18승 - 5957 - 1 = 262144 - 5957 - 1 = 256186 / z18
   N : 2의 18승 - 5964 - 1 = 262144 - 5964 - 1 = 256179 / z18

이렇게 계산을 하면 남쪽의 타일값이 북쪽보다 더 커지는데 이건 네이버지도와 반대이다.

위와 같이 적은다음 [Select entered coordinates]버튼을 누르면 오른쪽 화면에 옅은 빨간박스로 덮힌 지도가 나타난다.

왼쪽화면의 [Atlas Content]탭에서 [Add selection]을 누르면 Layer 18이 나타난다. 
   
오른쪽의 지도영역에서 마우스 오른쪽 버튼을 누른채 지도 오른쪽 끝으로 드래그를 하면 위그림처럼 선택안된 영역이 나타나는데 이건 mobac 의 오류일듯..

지도의 아무곳이나 클릭하면 희미한 빨간박스가 사라지면서 선택이 해제되고 그곳의 타일값이 화면왼쪽에 나타난다.


10. [Atlas Content]탭의 [Layer 18]을 선택한 뒤 마우스 오른쪽 버튼을 누르면 나타나는 창에서 [Display selected areas]를 선택한다. 

11. 지도의 선택영역이 희미한 노란색으로 나타난다. 마우스 오른쪽버튼을  누른채 드래그하여 지도의 전체영역이 올바르게 선택되어있는지 확인한다. 

Atlas Content의 Layer 18 글자위에 마우스를 가져다 놓으면 (클릭하지 않음) 정보창이 열리는데 [Maximum tiles to download]을 보면 숫자가 80이다. 80개의 이미지 파일을 선택했다는 뜻인데 s:\naver01 폴더밑에 있는 파일의 갯수를 헤아려보면  

18폴더안에 6711 부터 6720 까지 폴더의 갯수가 10개이고 각각의 폴더에 8개의 파일이 있으니까 총 80개의 이미지 파일이 있음을 알수가 있다. 



12. Create Atlas 를 누른다. 

13. 다음과 같은 창이 열리면서 지도가 만들어진다. [Open Atlas Folder]를 클릭하여 최종 완성본을 확인한다.

    아래의 창에는 0% done 이니 0 bytes ..같은 정보가 있어서 불완전한 지도가 만들어 진 것 같은 기분이 드는데 그건 전혀 그렇지가 않다. 여기서는 이미 받아둔 하드디스크 안에 있는 조각난 지도파일을 대상으로 오프라인 작업을 했기 때문에 이런 정보가 보이는 것이지 만약 직접 온라인을 통해서 지도를 다운로드 받으면서 작업할 경우에는 해당정보들이 다 채워진다. 여기서는 신경쓰지 말자. 


14  Layer 18.png 가 최종결과물이다. Layer 18.map파일은 무시. gps정보를 완전히 무시한 채 작업을 했기때문이다.


.끝..

sunding 속도계 자전거


아래쪽에 버튼이 3개 있는데 

왼쪽 = up
중간 = 모드변경
오른쪽 = set

밤에 중간버튼을 누르면 화면이 5초정도 밝아진다

중간버튼을 누르면 화면의 내용이 변한다.

화면 아래쪽에 dst, trip, odo 가 번갈아 나타난다.

dst 나 odo 가 나타났을때 [오른쪽]버튼을 몇초정도 지긋이 누르고 있으면 화면의 내용이 바뀐다.

이곳이 설정화면이다.

시간을 바꾸고, 온도의 단위를 섭씨 또는 화씨로 바꾸며, 거리의 단위를 km 또는mile로 바꾼다.

왼쪽버튼 : 설정값을 바꾼다.
오른쪽버튼 : 설정값의 위치를 바꾼다. (온도,거리,시간 등)

오른쪽버튼을 누를때마다 설정값의 위치가 바뀌면서 모든 설정값을 다 보여준다음 깜빡임이 멈추면서 이 모드를 빠져나온다.

나의 경우는 

01. 시간을 현재시간에 맞게 바꾸고
02. 내 타이어는 26 x 2.1 이니까 208.0 => 209.5 로 바꾼다.
    26 x 2.0 타이어를 사용할때는 208.0 => 207.4 로 바꾼다.

일반화면에서 왼쪽버튼을 지긋이 누르고 있으면 현재 dst와 ttm,tm값이 0으로 바뀐다.



gpx 파일로 mapsforge의 map파일 만들기 여행

절대참조 : https://github.com/mapsforge/mapsforge

  • 필요한 프로그램

01. python 설치 (2.7.x) 
   여기가서 읽어본뒤 이것을 다운로드 받으면 된다.
   s:\>osmosis-latest 에 압축을 풀었다고 가정

   많은 파일중에서 지금 현재는  mapsforge-map-writer-master-20180417.073816-214-jar-with-dependencies.jar 
   이 파일을 받는다. 아마 세월이 흐르면 날짜부분에 변화가 있을듯.

   s:\>osmosis-latest\lib\default 폴더에 위의 jar파일을 넣는다.


  • 한줄요약

gpx 파일을 읽어 그중 필요한 것만 뽑아내고 거기에 몇개를 더해서 osm파일을 만든뒤 이 osm파일을 재료로 map파일을 만든다. 

  • 세부사항

오룩스맵이나 산길샘, 가민,트랭글 같은 앱에서 gpx파일을 만들어내는데 그 파일을 편집기에서 열어보면 좀 복잡해보이는 xml형식이다.
아래그림은 처음 부분이고 


이건 마지막 부분이다.

알아보기 좋게 구조를 다시 적어보면 다음과 같다.


일정시간간격으로 gps위성의 신호를 받아와서 그 지점의 위치를 표시한게 <trkpt> 이고 적당한 지점을 이름으로 표시한곳이 <wpt>이다. 여기서는 [배드민턴]코트를 <wpt>로 표시했다.
위 그림에서 표현은 하지 않았지만 <trk>도 여러개가 있을 수 있고 <trkseg>도 여러개일 수 있다.

한달동안 산행을 했고 그것을 하나의 gpx파일에 기록했고 하루의 산행을 오전,오후로 구별했다면 다음과 같이 쓸 수 있다.

osm 파일의 예를 보자.

어떤 지역의 지도를 JOSM에서 받아와서 필요한 몇개만 추려냈다.

gpx와 같은 xml 파일형식이다.
gpx 자리에 osm이 적혀있고 wpt 가  node 에 , trkpt 가 way속의 nd 에 대응한다.

node와 way의 속성을 살펴보면 way에 위도,경도가 없는것을 빼고보면 둘다 동일한 속성을 가지고 있다.
osm(OpenStreetMap)은 열려있는 지도이기때문에 누구나 사이트에 등록만 하면 지도를 추가하고 편집할 수 있다. 그래서 user와 uid가 필요한것이다. 
위의 노드는 이름이 Aleks-Berlin 이고 uid가 85218 을 가진 분이 만든것이며 이 노드자체의 id는 442882724 이다. 
처음 이 노드가 만들어질때 딱 한번 숫자가 부여되면 바뀌지 않는다. 이 노드가 삭제되더라도 다른 노드에서 이 노드의 아이디를 가지지 못한다. 그야말로 고유아이디이다. 

처음 노드를 만들어서 서버에 올리기 전에 오프라인 저장한뒤 살펴보면 이 아이디가 음수로 나타나는데 이는 이 노드가 서버에 올려지지 않았다는 것을 의미한다. 음수이이디를 가진 노드가 서버에 올려지면 적절한 검사를 받은뒤 양수로 된 고유아이디를 부여받는다. 음수아이디를 가진 노드로는 map파일이 만들어지지 않는다.
gpsbabel 같은 프로그램에서 gpx를 osm으로 변환해보면 이 아이디가 음수로 표시되는것도 이러한 까닭이고 이 파일로는 map파일이 만들어지지 않는것도 이러한 이유에서이다. 

timestamp는 이 노드를 마지막 편집한 시간이고 version과 changeset는 노드의 값이 변할 때마다 적절한 값으로 바뀐다.

여기서는 gpx로 만든 지도를 osm서버에 올리지 않을 것이기 때문에 id의 값에 신경을 쓸 필요는 없다. 숫자1에서 시작할 것이다. 노드의 속성값이 너무 많은 듯 하여 하나씩 지워가며 map 파일을 만들어 보았는데 uid, user, changeset는 없어도 되고 시간은 같은 시간으로 적어도 상관없었다.

<node> 와 <way> 안에 <tag>가 있는데 node와 way의 모양과 이름을 나타낸다.

예를들어 

<node....>
   <tag k='name' v='들머리' />
   <tag k='amenity' v='cafe' />
</node>

<way....>
   <nd>....</nd>
   ..
   <tag k='highway' v='path'/>
</way>

오룩스맵에 올려본 모습인데 노드의 이름이 보이고 모양은 cafe 니까 커피잔이 나타났고 웨이의 모양은 점선으로 나타났다.

이 태그의 값에 따라 나타나는 모양은 다양하다. 잠시 여기로 가서 보고오자.

이제 gpx 파일에서 osm파일로 바꾸는 과정을 살펴보자.

먼저 파이썬에서 xml을 다루는 방법을 여기저기를 들락거리면서 공부했다. 그것을 바탕으로 아래의 코드를 작성했다.

파이썬에서 한글을 다루니까 제일처음에 다음문장을 적는다.
#*-* coding:utf-8 -*-

xml을 다루니까 다음을 추가.
import xml.etree.ElementTree as ET

gpx 파일을 연다.
tree = ET.parse('test.gpx')

최상위 태그를 가져온다.
src = tree.getroot()

src를 찍어보면 
{http://www.topografix.com/GPX/1/1}gpx

gpx만 나오는게 아니라 네임스페이스(namespace)가 앞에 붙어있다.
위 그림처럼 xmlns= 의 뒤에 있는 값이다. 모든 gpx파일의 네임스페이스가 이 값일텐데 그래도 장담을 못하니까 프로그램내에서 이값을 구해서 사용한다.

ns = src.tag.split('}')[0].strip('{')

이렇게 하면 ns의 값에 http://www.topografix.com/GPX/1/1 이 들어온다.

중괄호를 넣는다.
nss = '{'+ns+'}'

중괄호가 들어있는 nss는 검색을 하는데 사용하고 ns는 다음과 같이 등록하는데 사용한다.
ET.register_namespace('',ns)

이렇게 하면 최종적으로 osm파일이 만들어질때 네임스페이스가 잘 만들어진다. 위 문장을 넣고 안넣고에 따라 osm파일이 어떻게 달라지는가는 마지막쯤에서 보여주겠다.

osm파일의 루트노드를 만든다. 속성으로 버전만 넣는다.
dst = ET.Element('osm',version='0.6')

gpx태그 바로 밑에 있는 태그중 <wpt>들을 모두 가져온다.
wpts = src.findall(nss+'wpt')

그중에 하나의 자료다. 빨간박스친 부분만 쓰인다.

위 그림과 같은 <wpt>를 아래그램과 같은 <node>로 바꾼다

    times = '1999-12-25T01:01:01Z'
    count = 1
    tagNode1 = ET.Element('tag',k='amenity',v='cafe')

    for wpt in wpts:
        node = ET.Element('node', id = str(count), version='1', timestamp=times)
        count += 1
        node.attrib['lat'] = wpt.attrib['lat']
        node.attrib['lon'] = wpt.attrib['lon']
        node.append(tagNode1)

        tagNode2 = ET.Element('tag',k='name')
        tagNode2.attrib['v'] = wpt.find(nss+'name').text.strip()
        node.append(tagNode2)

       dst.append(node)

       makeBounds(wpt.attrib['lat'],wpt.attrib['lon'])

count의 값은 첫번째 node에서 1이고 하나씩 증가하는데 이값을 노드의 id로 사용한다. 시간은 고정값을 사용하고 version도 1로 고정값을 사용했고 두개의 tag중 하나는 고정값으로 amenity=cafe를 사용했다. 이 값을 바꾼다든가 tag하나를 더 추가한다든가 하는것은 위의 코드를 참조해서 상상력을 조금만 발휘하면 된다.

더 진행하기전에 gpx파일의 <metadata>의 <bounds>에 대해서 살펴보고가자.

gpx파일속에 사용된 모든 포인트의 경위도값을 비교해서 구한 최소값과 최대값을 저장하고 있다.
이 <bounds>가 osm에서도 사용되는데 이 태그의 값이 gpx파일를 생성하는 앱에 따라서는 없는경우도 있으므로 여기서는 직접 구하는 것으로 한다.

minlati = '90.0'
minlong = '180.0'
maxlati = '0.0'
maxlong = '0.0'

def makeBounds(lat,lon):
    global minlati,minlong,maxlati,maxlong

    if lat < minlati : minlati = lat
    if lat > maxlati : maxlati = lat
    if lon < minlong : minlong = lon
    if lon > maxlong : maxlong = lon

최소.최대값을 전역변수로 뽑아냈고 적당한 연산을 하는 함수로 만들었다. 실수로 변환하지 않고 문자열비교를 했는데 그것은 비교대상의 정수부값이 같은 자리수를 가지기에 그렇게 했다. 하나의 gpx파일안에 위도가 35.xxx 와 2.xxx가 같이 나오는 경우라든가 경도값으로 127.xxx와 12.xxx와 같이 정수의 자리수가 다른경우가 나올때는 이렇게 문자열비교를 할 수 없다. 완전 범용으로 만들려면 위 함수를 손봐야하는데 여기서는 이대로 사용한다.

gpx에서는 <trkpt>라는 태그로 시간순으로 쭉 늘어놓았는데 osm에서는 그렇게 하지 않고 모든 trkpt를 각각 node로 만든뒤 way라는 태그에서 해당 node의 id값을 쭉 적는다.

<trkpt lat='xx1' lon='xx'></trkpt>
<trkpt lat='xx2' lon='xx'></trkpt>
<trkpt lat='xx3' lon='xx'></trkpt>
<trkpt lat='xx4' lon='xx'></trkpt>

위와 같은 gpx파일속의 문장이 아래과 같이 두개의 부분으로 쪼개진다.

<node id ='1' 'lat='xx1' lon='xx' .../>
<node id ='2' 'lat='xx2' lon='xx' .../>
<node id ='3' 'lat='xx3' lon='xx' .../>
<node id ='4' 'lat='xx4' lon='xx' .../>

<way id='xx' ...>
   <nd ref='1' />
   <nd ref='2' />
   <nd ref='3' />
   <nd ref='4' />
</way>

이 부분을 처리해보자

    ways = []

    for trkseg in src.iter(nss+'trkseg'):
        way = ET.Element('way',version='1',timestamp=times)
 
        for trkpt in trkseg.findall(nss+'trkpt'):
            node = ET.Element('node',id = str(count),version='1',timestamp=times)
            node.attrib['lat'] = trkpt.attrib['lat']
            node.attrib['lon'] = trkpt.attrib['lon']
            dst.append(node)
            nd = ET.Element('nd',ref=str(count))
            count += 1
            way.append(nd)
            makeBounds(trkpt.attrib['lat'],trkpt.attrib['lon'])

        tag = ET.Element('tag',k='highway',v='path')
        way.append(tag)

        ways.append(way)

    for way in ways:
        way.attrib['id'] = str(count)
        count += 1
        dst.append(way)

osm 파일에서는 모든 node 를 다 적은뒤 way가 따라온다. 그래서 
trkpt를 node와 way로 나누어 node는 dst에 넣고 way는 따로 ways 리스트에 보관한뒤 node 작업이 다 끝난뒤 ways 리스트에서 way를 불러와서 dst에 넣었기 때문에 코드가 약간 뒤틀렸지만 이해못할 바는 아니다.

bounds를 osm파일제일 위에 삽입한다.
bounds = ET.Element('bounds',minlat = minlati, minlon = minlong, maxlat = maxlati, maxlon = maxlong)
dst.insert(0,bounds)


이제 osm파일을 만든다.
ET.ElementTree(dst).write('test.osm', encoding='utf-8',xml_declaration=True)

map파일을 만든다

s:\>osmosis-latest\bin\osmosis --rx s:/test.osm --mw file=s:/test.map


전체 프로그램 소스는 다음과 같다.

#*-* coding:utf-8 -*-
import xml.etree.ElementTree as ET

filename = 's:/test.gpx'
osmfile = filename + '.osm'
mapfile = osmfile + '.map'

times = '1999-12-25T01:01:01Z'

node_k = 'amenity'
node_v = 'cafe'

way_k1 = 'highway'
way_v1 = 'path'

minlati = '90.0'
minlong = '180.0'
maxlati = '0.0'
maxlong = '0.0'

def makeBounds(lat,lon):
    global minlati,minlong,maxlati,maxlong

    if lat < minlati : minlati = lat
    if lat > maxlati : maxlati = lat
    if lon < minlong : minlong = lon
    if lon > maxlong : maxlong = lon

def gpx2osm(file):

    tree = ET.parse(filename)

    src = tree.getroot()

    ns = src.tag.split('}')[0].strip('{')
    # 'http://www.topografix.com/GPX/1/1'

    nss = '{'+ns+'}'
    # '{http://www.topografix.com/GPX/1/1}'

    # gpx파일의 네임스페이스가 없을경우 
    if src.tag == 'gpx':
        ns = nss = ''

    ET.register_namespace('',ns)

    dst = ET.Element('osm',version='0.6')

    wpts = src.findall(nss+'wpt')

    tagNode1 = ET.Element('tag',k=node_k,v=node_v)
    
    # id 의 시작숫자 
    count = 1

    for wpt in wpts:
        node = ET.Element('node', id = str(count), version='1', timestamp=times)
        count += 1
        node.attrib['lat'] = wpt.attrib['lat']
        node.attrib['lon'] = wpt.attrib['lon']
        node.append(tagNode1)

        tagNode2 = ET.Element('tag',k='name')
        tagNode2.attrib['v'] = wpt.find(nss+'name').text.strip()
        node.append(tagNode2)

        # 높이를 표현하고 싶을때 사용함 
        # 소수점 첫째자리만 사용
        # txtEle[1][:2] 이렇게 바꾸면 둘째자리까지 사용

        # tagNode3 = ET.Element('tag',k='ele')
        # txtEle = wpt.find(nss+'ele').text.split('.')
        # txtEle = txtEle[0] + '.' + txtEle[1][:1]
        # tagNode3.attrib['v'] = txtEle
        # node.append(tagNode3)

        dst.append(node)
        makeBounds(wpt.attrib['lat'],wpt.attrib['lon'])

    ways = []

    for trkseg in src.iter(nss+'trkseg'):
        way = ET.Element('way',version='1',timestamp=times)

        for trkpt in trkseg.findall(nss+'trkpt'):
            node = ET.Element('node',id = str(count),version='1',timestamp=times)
            node.attrib['lat'] = trkpt.attrib['lat']
            node.attrib['lon'] = trkpt.attrib['lon']
            dst.append(node)
            nd = ET.Element('nd',ref=str(count))
            count += 1
            way.append(nd)
            makeBounds(trkpt.attrib['lat'],trkpt.attrib['lon'])

        tag = ET.Element('tag',k=way_k1,v=way_v1)
        way.append(tag)
 
        ways.append(way)

    for way in ways:
        way.attrib['id'] = str(count)
        count += 1
        dst.append(way)

    bounds = ET.Element('bounds',minlat=minlati,minlon=minlong,maxlat=maxlati,maxlon=maxlong)
    dst.insert(0,bounds)

    ET.ElementTree(dst).write(osmfile, encoding='utf-8',xml_declaration=True)

def osmosis():
    import os
    cmd = 's:/osmosis-latest/bin/osmosis'
    cmd += ' --rx ' + osmfile 
    cmd += ' --mw file=' + mapfile

    print(cmd)
    os.system(cmd)

gpx2osm(filename)
osmosis()    

변환할 gpx파일이름을 test.gpx 로 가정
전체 파이썬소스이름을 gpx2map.py 로 가정.
둘다 s:\ 에 있다고 가정.

cmd창을 열어서 s:\ 폴더로 간다음 

s:\>python gpx2map.py 라고 하면 된다.

그러면 s:\ 폴더에 
test.gpx.osm 
test.gpx.osm.map

이 만들어진다.

map파일을 오룩스맵에 올려서 살펴보면 줌레벨 14 부터 패스가 보이고 줌레벨 17이 되어야 커피잔이 보인다.
node의 tag값이 amenity=cafe 이고 
way의 tag값이 highway=path 이기 때문이다.

여기로 가서 잠시 살펴보자.

<osm-tag key="highway" value="path" zoom-appear="14" />
<osm-tag key="amenity" value="cafe" zoom-appear="17" />

기본값이 위와 같이 정해져있다.

커피잔이 16레벨 부터, 패스는 15레벨부터 보이기를 원한다면 그렇게 할 수 있다.

매핑파일을 따로 만들어서 osmosis 실행할때 넣어주면 된다.

<?xml version="1.0" encoding="UTF-8"?>
<tag-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" default-zoom-appear="16"
    profile-name="default-profile" xmlns="http://mapsforge.org/tag-mapping"
    xsi:schemaLocation="http://mapsforge.org/tag-mapping https://raw.githubusercontent.com/mapsforge/mapsforge/master/resources/tag-mapping.xsd">

    <osm-tag key="highway" value="path" zoom-appear="15" />
    <osm-tag key="amenity" value="cafe" zoom-appear="16" />
</tag-mapping>

이런파일을 만들어서 이름을 적당히 준다음(test.xml 이라고 하자) osmosis 함수안에서 다음과 같이 한문장을 추가하면 된다.

    cmd = 's:/osmosis-latest/bin/osmosis'
    cmd += ' --rx ' + osmfile 
    cmd += ' --mw file=' + mapfile
    cmd += ' tag-conf-file=s:/test.xml'



네임스페이스 등록관련해서 빠트린 설명을 이제 해본다.

osm 파일을 열어서 osm속성을 보면 따로 네임스페이스 항목이 없다. 

<osm version="0.6">

파이썬 프로그램에서 gpx파일의 태그를 복사한게 하나도 없기때문에 생긴 요행이다. <bounds>를 구할때 gpx에 있는 <bounds>를 그대로 받아서 쓰는걸로 코드를 수정하면 결과는 달라진다.

...
metadata = src.find(nss + 'metadata')
bounds = metadata.find(nss + 'bounds')
dst.append(bounds)

이렇게 코드를 바꾸어서 실행하면 

<osm xmlns="http://www.topografix.com/GPX/1/1" version="0.6"><bounds maxlat="35.2304286" maxlon="129.1069870" minlat="35.2208487" minlon="129.1012863" />

osm의 속성으로 xmlns가 생겨났다. 

이제 네이스페이스를 등록하는걸 하지 않고 실행하면 즉 ET.register_namespace('',ns) 을 지우고 실행하면 

<osm xmlns:ns0="http://www.topografix.com/GPX/1/1" version="0.6"><ns0:bounds maxlat="35.2304286" maxlon="129.1069870" minlat="35.2208487" minlon="129.1012863" />

ns0가 첨가되어있다. 이 osm파일로는 map파일이 만들어지지 않는다.


map파일을 오룩스맵에서 합성지도(밑에 다음지도를 넣고 위에 방금만든 map파일을 올린경우)로 만들어 띄워보면 이런 모습이다.
등산로를 나타내는 선이 눈에 거슬릴 정도로 굵다. 
highway=path 대신에 딴걸 넣어서 돌려봐도 신통치가 않다. 위 그림의 녹색 등산로 정도의 굵기에 점선정도면 좋을 듯하다. 

렌더링관련 문서를 읽어보면 어렴풋이 답이 보이나 명확하지는 않다. 일단 간단하게 아래와 같은 파일을 만들어보았다.

파일이름을 mypath.xml 이라고 주고 이 파일을 오룩스맵의 mapstyles 폴더에 넣어줬다. 그리고나서 mapsforge 테마를 mypath로 바꾸어주면 다음과 같이 나타난다.
색깔이 약간 밝은게 흠이나 굵기와 점선은 마음에 든다. 그런데 치명적으로 커피잔이 보이지 않는다. 파일을 이렇게 만들면 기존에 있던것은 그래도 두고 새로 정의한 것만 바뀌기를 기대했었는데 그런게 아닌 모양이다. 기본값으로 가지고 있던 모든 값이 다 사라지고 path하나만 정의된 듯. 커피잔 항목을 만들어서 추가하면 문제는 해결되나 이런 해결은 잠정적으로 위험을 내포하고 있다. 바탕지도로 벡터맵을 깔고 그 위에 방금 만든 map을 올리면 바탕지도가 보이지 않게된다. 기본정의가 하나도 없고 단지 path와 cafe두개의 정의만 있기때문이다. 

기본값이 정의되어 있는 xml파일을 찾아서 그곳의 highway=path항목만 바꾸는게 최선일 듯하다.

여기들어가서 오른쪽 위쪽의 녹색버튼을 눌러 다운로드한다.


mapsforge-master.zip\mapsforge-master\mapsforge-themes\src\main\resources\assets\mapsforge\default.xml

위 파일이 기본값을 정의한 파일이고 이 파일에서 사용하는 아이콘그림과 패튼이 들어있는 폴더는 아래와 같다.

mapsforge-master.zip\mapsforge-master\mapsforge-themes\src\main\resources\assets\symbols
mapsforge-master.zip\mapsforge-master\mapsforge-themes\src\main\resources\assets\patterns

default.xml 에서 path 항목을 찾아 적당한 값으로 바꾼뒤 그 파일과 위의 두개의 폴더를 오룩스맵의 mapstyles폴더에 넣으면 된다.

그렇게 한 뒤 mapsforge의 테마로 [default]를 선택한다.
이렇게 진행하기 위해서는 먼저 default.xml에서 path항목을 찾아야한다. 

126라인의 path는 아니다.  tunnel = true 즉 터널속의 path는 우리가 찾는 패스가 아니다.


227라인의 path도 아니다. area=yes 즉 공간또는 건물을 나타내는데 쓰이는 path이다.

458라인의 path도 아니다. bridge=yes 즉 다리위의 path이다.

571라인의 path가 바로 그 path다. tunnel=no, area=no 
stroke-dasharray = "5,5" 로 바꾸었고 stroke-width="0.2"로 바꾸었다.
전반적으로 바꿔져야 할 것이 하나 더 있다.

다음 그림을 보자.
20라인을 보면 jar가 붙어있다. jar대신에 file 로 바꾼다. 현재 이 파일속에는 jar가 128군데 있는데 다 바꾼다.

default.xml의 수정이 정확하게 끝났다면 path가 이렇게 나타난다. 물론 커피잔은 기본값이다.

default.xml 을 손대기 시작한 김에 조금만 더 설명을 해보자.

등산로의 선굵기와 이정표의 그림을 메뉴로 띄워 선택해볼 수도 있다.
아래그림처럼 stylemenu 태그를 삽입한다.

레이어를 다섯개 만들었는데 마지막 레이어가 메뉴역할은 한다. overlay 각각이 하나의 메뉴를 뜻한다.
오룩스맵에서 어떻게 보이는지 먼저 살펴보자.
아래그림처럼 Mapsforge 테마를 열어서 [default]를 선택하면 선택과 동시에 창이 닫히는데 다시 열어서 이제는 톱니바퀴를 누르면 메뉴가 나타난다.

메뉴가 4개 있다. 각각이 하나의 레이어를 나타낸다. 
등산로 0.1은 layer id 가 my_path01 인 레이어를 나타내는데 cat id= path01를 품고있다. cat는 카테고리를 뜻한다.
cat id와 layer id는 이름을 같게 둘수도 다르게 둘수도 있다. 
등산로 0.1을 선택한다는 이야기는 카테고리 path01을 선택한다는 말과 다르지 않다. 메뉴에 직접 카테고리를 집어넣으면 레이어없이 두단계만으로 충분할 것도 같은데 이렇게 한 이유는 하나의 메뉴로 여러개의 카테고리를 왕창 선택할려면 이런식의 중간단계를 거치는게 자연스럽다. 여기서는 카테고리 하나에 레이어 하나니까 레이어의 장점이 보이지가 않는데 하나의 레이어에 여러개의 카테고리를 집어넣을 수 있다는 것을 알면 이런 구성방법이 어색하지 않다

해당되는 <rule>을 찾아서 위의 그림처럼 메뉴에서 정의한 이름대로 cat항목을 추가한 그림이다. rule자체가 각각 하나씩 늘어났다.

등산로 0.1과 picnic을 선택하면 이렇게 보인다. 등산로의 굵기가 다음지도의 녹색등산로 굵기보다 얇고 이정표로 나무 피크닉테이블이 나타났다. 봐줄 만하다.
등고선을 표시하는 맵스타일을 여기가면 구할 수 있는데 6000라인쯤 되는 xml파일을 열어서 읽어보자!
그러면 테마렌더링에 관해 더 깊게 이해할 수 있다. 


amd64 또는 x64 에서의 기계어 분석 #4 (fpu #3) asm

예외비트에 대한 설명이 계속된다.
  • DE(Denormal Exception)와 DM(Denormal Mask)

상태워드와 제어워드의 두번째 비트(b1)가 Denormal 에 관한것이다. 최소값보다 더 작은수가 스택으로 올라오든지 또는 산술연산의 오퍼랜드로 쓰일때 이 예외가 발생한다. 마스크의 역할은 IE때와 같다. 마스크를 쓰고 있으면 (DM비트가 1,초기값) 예외처리를 하지 않고 그 다음명령을 진행한다. 벗고 있으면(DM비트가 0) 예외처리루틴으로 분기한다. 목적지로 지정된 스택은 보전된다.

DE가 발생하는 두가지 경우를 각각 나누어 살펴본다.

1) 레지스터 스택으로 올라올때
  스택의 크기는 10바이트이니 싱글이나 더블타입의 디노말한 데이타가 올라오더라도 감당할 수 있다. 그렇지만 일단 DE를 1로 만들어 denormal 데이타가 올라왔다는 것을 알린다. 마스크를 쓰고 있으면 그 다음 명령을 수행한다. 스택으로 볼때는 크기에 여유가 있기 때문에 계산하는데 어려움이 없다. 그러나 더블확장타입의 디노말한 데이타가 올라오면 DE를 1로 만들지 않는다. 

라인8까지 실행되었을때의 상황이다. denormal한 싱글데이타가 스택으로 올라왔고 DE 가 1로 변했다. DM이 1이니까 라인9를 실행할것이다. 
제어워드 027f = 0000 0010 0111 1111
상태어드 3802= 0011 1000 0000 0010

라인11까지 실행되었을때의 모습이다. denormal 한 수이지만 정확한 덧셈이 되었다. 덧셈결과는 denormal 이 아니다. 지수비트에 1이 하나 나타났다. 

00e00603
0000 0000 1110 0000 ....0011

더블일때도 눈으로 확인하자.

DE비트가 1로 변했으나 덧셈이 잘 수행되었다.

이제 더블확장이다. 이 때도 덧셈이 잘 수행될까?

더블확장(double extended) 10바이트짜리 디노말한 수 두개를 스택으로 불러들였을때의 상황이다.
숫자끝에 #DEN 이라는 약어가 붙어있다. DENormal을 의미한다. 딱지를 붙여 디노말을 나타냈는데 상태워드에는 DE 비트가 1로 변하지 않았다. fpu는 이 숫자가 노말인지 디노말인지 알지 못하지만 visual studio 디버거에서는 이 값이 디노말임을 아는듯하다. 그런데 tDe1과 tDe2를 long double 로 인식했음에도 값이 영 엉망으로 표시되어있다. 이건 약간 부조화로 보인다. 여하튼 더블확장은 다른 데이타형과 달리 스택에 불러들여도 DE비트가 1로 변하지 않는다.
덧셈을 수행하면 어떤 일이 벌어지는지 살펴보자.

상태워드의 값이 3A32 이다.

0011 1010 0011 0010
다시 한번 상태워드의 비트별 의미에 대해 잠시 보고오자.
아래쪽 예외관련 비트만 표시된 그림이다.

DE 이외에도아직까지 다루지 않았던 PE, UE 가 추가로 1로 변했다.
st0에 있는 값은 신뢰할 수 없는 수라는 것을 비트 1개가 아니라 비트 3개로 강력하게 주장하고 있다.
그런데 숫자를 보면 계산이 된 듯한 느낌을 받는다.

+1.050721609747e-4933#DEN
+1.313450044831e-4933#DEN
+2.364171654578e-4933#DEN

눈으로 빠르게 훑어봐도 계산결과가 틀림이 없어보인다. 그러나 이 숫자들이 소수점 아래 4933 자리라는걸 감안하면 신뢰도가 확 떨어지는것도 사실이다. 여하튼 결과는 이렇게 나온다.

지금까지는 마스크를 쓴 상태 였는데 마스크를 벗기면 어떻게 결과가 달라지는지 싱글데이타 하나만 살펴보고 끝내자.

라인15가 실행되지 못하고 에러상자가 떴다.

제어워드의 값이 037D
0000 0011 0111 1101

DM 이 0이다. DE의 마스크를 벗겼다는 의미

상태워드의 값이 B882
1011 1000 1000 0010

b15, b7, b1 가 1이다.

DE이외에도 ES와 Busy 비트까지 1로 변해있다.
DE가 발생했으나 여태까지는 마스크가 씌여줘 있었으니 속은 타지만 묵묵히 그 다음 명령을 수행했지만 지금은 마스크가 벗겨진 상태. 일종의 고삐가 풀린 상태이니 에러처리 루틴으로 달려간다.그때 ES비트가 1로 변한다. 이 비트는 DE와만 관계를 맺고 있는 비트가 아니라 모든 예외처리비트와 관계가 깊다. 어떤 예외라도 예외처리루틴이 실행되고 있다면 ES비트가 1로 변한다. 그리고 ES비트의 복사본이 Busy 비트이다. busy비트는 8087 fpu와의 호환성을 위해서 아직까지 존재하지만 딱히 쓰이지는 않는다. ES비트를 읽든 busy비트를 읽든 결과는 같다. 이들 예외관련비트 8개와 busy비트 1개는 스티키비트다. 다시 원래대로 초기값으로 돌릴려면 fclex/fnclex, finit/fninit 이들 명령어를 사용하든지 아니면 상태비트가 포함된 모든 fpu환경을 메모리에 저장했다가 ( fsave/fnsave, fstenv/fnstenv) 적당히 손본 뒤 다시 읽어들이면된다.(frstor, fldenv) 

2) 산술연산의 오퍼랜드로 쓰일때

위에서 예를 들면서 이 디노말한 수를 대상으로 덧셈을 해 보았는데 그때 DE가 1로 변한다는 사실을 확인했다. 다른 산술연산에서도 마찬가지 일터. 하지만 fabs 명령어를 통해서 한번 만 더 확인하자. 이 명령어는 st0의 값을 양수로 만드는 명령이다. 
놀랍다. DE가 변하지 않는다. fchs도 변하지 않는다. 삼각함수(fsin)에서는 변했다. frndint 도 변했다. 단항으로 몇개 해봤는데 fabs와 fchs 빼고는 다 DE가 1로 변했다. 이 두개의 명령어만 유의하면 되겠다.

  • ZE(Zero divide Exception)와 ZM (Zero divide Mask)
상태와 제어워드의 세번째비트(b2)이다. 

앞에서 살펴본바있고 따로 익혀야 할 새로운 개념이 없기때문에 설명은 삼가고 몇가지 생소한 것만 추가한다.

나누는 명령은 여기에 정리되어있다. 0 으로 나누면 다 ZE가 발생한다. 

나머지를 구하는 명령인 fprem1 도 여기에 해당되는 듯하다. 그러나 이 명령은 ZE가 아니라 IE만 발생시킨다.

나누기 명령은 아니지만 내부적으로 나누기를 사용하는 명령어가 두개 있다.

fyl2x 와  fxtract 이다.

둘다 st(0) 에 0 이 들어있으면 ZE를 1로 만들며 #INF 무한대를 반환한다.

상태워드의 값이 3804 이다.

0011 1000 0000 0100

ZE비트가 1로 변한걸 확인할 수 있고 st0의 값이 #INF로 변했다.

반환된 결과값인 무한대의 값이 ff 80 00 00 이다. 부호비트가 1이니까 이건 -∞ 이다. st(1)에 넣은 파이값이 양수이니까 그것의 반대인 -∞가 나온다. 음수를 집어넣으면 +∞가 반환된다. 확인해보자

결과값이 7f 80 00 00 이다. 부호비트가 0 이므로 +∞ 다. 

fxtract 는 st(0)의 값을 읽어서 지수부와 가수부로 분리한 뒤 지수부(바이어스가 풀린 원래의 지수값)를 스택에 넣고 이어서 가수부를 스택에 넣는 명령이다. 그런 이유로 지수부는 st(1)에 있고 st(0)에 가수부가 있게된다. st(0)에 0을 넣고 이 명령을 수행하면 st(1)에 -∞가 들어가고 st(0)는 변화가 없다. 즉 +0 을 넣었으면 +0 이 남아있고 -0을 넣었으면 -0이 남아있게 된다.

예상한 대로의 결과가 나왔다. ZE가 1로 변했고 st(0)에 +0, st(1)에 #INF 가 보인다. 이 무한대는 -무한대 일텐데 직접 찍어보지는 않았다.

이제는 -0을 넣고 다시 해본다.

ZE가 1로 변했다. 라인9에서 스택의 내용을 서로 자리바꿈 하였기때문에 st(0)에 #INF가 나타났고 찍어보니 

ff 80 00 00  -∞ 다

st(1)에는 -0 이 들어있다.

  • OE ( Overflow Exception) 와 OM ( Overflow Mask )

상태워드와 제어워드의 네번째(b3) 비트이다. 

명령의 결과가 목적지 데이타 타입의 최대값보다 클경우에 발생한다.

싱글일때와 더블,더블확장일때의 최대값은 여기에 적혀있다.

예제를 보자
싱글최대값을 스택에 넣었고 그 스택의 값과 메모리에 있는 값을 더해서 다시 스택에 넣었을때의 모습이다. 상태워드를 보니 예외비트에 변화가 없다. 즉 OE가 일어나지 않았다. 싱글 최대값을 서로 더해서 구한값은 싱글의 최대값은 넘어섰으나 스택의 최대값에는 미치지 못하기 때문이다. 스택은 10바이트 더블워드다. 그러나 그 값을 싱글에 집어넣으면 OE가 발생한다. 라인10을 실행한 화면을 보도록 하자.
상태워드의 값이 3A28 이다.

0011 1010 0010 1000

b3인 OE가 1로 변했다. 오버플로우가 발생했고 그 값이

7f 80 00 00+∞ 다.

싱글의 최대값이야 기껏 10의 38승 정도인데 스택의 최대값은 10의 4932승이다. 도대체 흘러넘치지 않을 값처럼 보이기도 하지만 scale 명령 한번에 담박에 넘친다.

스택이 #INF 무한대가 되었고 OE가 1로 변했다. fscale의 결과값이 목적지인 스택의 최대값을 넘어섰다는 이야기다. 라인11로 그값을 살펴보니 역시 무한대값이다.

메모리니까 리틀엔디언 형식으로 저장되어 있다. 상위바이트부터 읽어야 정상적인 값이 된다.

7f ff 80 00 00 00 00 00 00 00 

더블확장의 +∞ 표시다.

오버플로우가 발생했을때 그 값이 무한대로 나타났는데 그걸 미세조정할 수 있다.
무한대가 아니라 최대값으로 나타나게 할 수가 있다. 제어워드의 rc 값을 바꾸면 된다.
초기값이 round nearest ( 00 ) 인데 이때는 ±∞ 가 나타나고
round down ( 01 ) 일때는 음수쪽은 그냥 -∞ 가 나타나지만 양수쪽에서는 최대값이 나타난다.
round up (10)은 반대. 음수쪽에서 -최대값이 나타난다.
round zero (11)는 둘다. 음수쪽으로는 -최대값, 양수쪽으로는 +최대값이 나타난다.

확인해보자
round down으로 바꾸니 OE가 발생했음에도 무한대가 아니라 최대값이 나타났다. 음수쪽에서는 어떻게 되나 확인해보자.

OE가 발생했고 값은 #INF이다. 표시는 되어있지 않지만 이건-∞ 이다. 라인15에서 1을 -1로 만들었기 때문이다. 그대로 두고 round up 으로 바꾸면 #INF가 아니라 -최대값이 표시된다. 확인해보자.

이렇게 rc의 값으로 오버플로우했을때의 결과값을 조정할 수 있다. 
정리하면 다음표과 같다.

rc결과
00
nearest
-∞ .. +∞
01
down
-∞ ..+최대값
10
up
-최대값 .. +∞
11
to zero
-최대값 .. +최대값

OE의 마스크를 벗기면 무슨 일이 일어날까?

목적지의 데이타를 훼손시키지 않고 바로예외처리루틴으로 가버리는게 일반적인 방식인데 목적지가 스택인 경우에는 목적지의 데이타에 훼손이 일어난다.

아래의 그림을 보자.

OE 마스크를 벗긴 상태에서 즉 OM을 0으로 만든 상태에서 더블확장의 최대값을 스택에 두번 넣어 두개의 합을 구해본것이다. 라인15까지 실행되었고 OE가 발생했으며 ES와 Busy비트까지 1인걸 보니 예외처리루틴으로 분기한 상태이다.

b088
1011 0000 1000 1000

그런데 st0의 값을 보라.

+1.8336038675548471e-2466

최대값끼리 서로 더했는데 값은 엄청나게 작은 수가 나왔다. 10의 마이너스 2466승이다. 마치 정수계산의 오버플로우을 보는듯하다.

이 숫자가 어떻게 해서 나온건지를 추적해보자.

OE 예외가 발생했고 마스크가 벗겨져 있으면 OE예외처리루틴을 실행하게 되는데 그 루틴이 다 실행된뒤 프로그램이 에러메시지를  뿌리면서 종료하는 경우는 별 문제가 안되지만 계속해서 실행하는 경우에 대한 대책과 관계가 있다. 결과값이 너무 커서 오버플로우가 발생했으니까 그 값을 작게 만들어 다음 연산에 대비하는 방식인데 값을 작게 만드는 방법에 관한 것이다. 두가지 경우가 있는데
하나는 결과값이 메모리에 저장될때 발생한 OE 이고 다른 하나는 스택에 저장될때 발생한 OE이다.

위의 그림은 결과값이 스택에 저장될때의 경우이다.

스택의 크기가 10바이트라서 오버플로우가 발생했으니까 10바이트보다 큰 공간이 있다고 치고 그곳에서 계산을 한다.

1) 계산의 결과값을 지수와 가수로 분리한다.
2) 가수는 현재의 반올림규칙에 맞게 반올림한다. 올림이 발생하면 상태워드의 b9인 c1 플래그를 1로 만들고 내림이 발생하면 0을 만든다. 
3) 지수는 지수의 값에서 24576을 뺀다.

24576은 ( 2의 13승 ) * 3 의 값인데 지수의 최대값이 (2의 15승) -2 이니까 약 3/4 지점이다. 왜 이 숫자이어야 하는가에 대해선 아는바 없다. 그럴려니 한다.
결과값이 만약 100,000쯤 되면 24576을 빼더라도 오버플로우 상태에 있게된다.그때에는 숫자줄이는짓을 더 이상 하지 않는다. 그냥 ∞를 표시해버린다.
그렇다면 그 경계값은 얼마일까?

(2의 15승 ) -2 + ( (2의 13) * 3 ) = 57342

1만 더해져도 무한대가 된다.
종이에 줄을 쭉 그어 놓고 계산을 해보면 이 값이라는 걸 금방 알 수 있다.
지수의 최대값이 ( 2의 15승 ) -2 라는 사실이 이해가 안될 수가 있겠는데 잠시 이 를 훑어보자.
지수비트 15개가 다 1로 채워져 있는건 따로 예약이 되어있다. 그래서 11...10 이 최대값이다.

11....11 (1이 15개) = ( 2의 15승 ) -1
11....10(1이 15개) = ( 2의 15승 ) -2

이런 기초지식을 가지고 위의 값을 찾아보자.

가수부터 시작한다.

 ff ff ff ff ff ff ff ff (8바이트)
 ff ff ff ff ff ff ff ff (8바이트)
이 둘을 더하면 
ff ff ff ff ff ff ff fe

자리 올림이 발생했고 가수값은
ff ff...ff fe 가 된다. 자리올림한 1은 지수값에 더해진다.
끝자리가 반올림된건 아니다. 이건 앞자리가 올라간 것이다. 

지수는 7ffe + 1 = 7fff = 32767
32767 - 24576 = 8191 = 1fff

지수와 가수를 이어붙이면
1f ff ff ff...fe

확인해보면
일치한다.

앞에서 57342 가 경계값이라고 구했는데 그것도 확인해보자. 확인에 앞서 한가지 짚고 넘어갈 것이 있다. 이 지수의 값은 바이어스가 된 값이라서 실제값은 여기에 바이어스값을 빼줘야한다는 것이다. 더블확장의 바이어스값은 16383 이니까 

57342 - 16383 = 40959

이 값을 fscale 명령어의 지수값으로 사용하면 된다. 


  • UE( Underflow Exception )과 UM( Underflow Mask )
상태와 제어워드의 다섯번째(b4) 비트이다.

위에서 살펴본 오버플로우의 반대다. 오버플로우는 목적지 자료형의 최대값보다 클때 발생하는데 그 반대로 최소값보다 더 작을때 발생하는것이 언더플로우 예외 (UE)이다. 여러 자료형의 최소값을 보고 오자. 

10의 마이너스 1000승 ( 1.0e-1000 ) 쯤 되면 싱글과 더블의 최소값보다 작으니까 UE을 일으킬것 같으나 눈으로 봐야 확신할 수 있다.
스택에 e-1000 을 넣은뒤 싱글에 저장해본 것이다.

상태워드의 값이 3830
0011 1000 0011 0000

b4 (UE)가 1로 변했다. 싱글값이 1.1에서 0 으로 바뀌었다. 한계를 벗어나게 작은수라 깔끔하게 0 으로 처리한다. 역시 디버거상에 표시된 10바이트 더블확장 (long double)의 값은 정확하지 않다. vs버전이 낮아서 그런건가? 최신버전에서는 고쳐졌을라나?

싱글에서 UE를 확인했으니까 더블에서도 확인하자.
싱글과 결과가 같다.

더블확장 데이타는 load 와 store로는 UE를 만들어내지 못한다. 그건 DE살펴볼때도 본 바있다.

오버플로우일때는 한계값을 벗어났을때 상태워드의 rc 값을 조정하면 무한대가 아니라 최대값으로 나타나게 할 수 가 있었는데 언더플로우에서도 그게 가능할까? 즉, 0이 결과값에 나타나는게 아니라 최소값이 나타나게 하는게 가능할까?

가능하지 않다 !

레지스터가 언더플로우되게 적당한 수를 선택해서 곱셈을 해보자.
두수를 곱해 더블확장의 최소값보다 작게 만들면 UE가 발생할 터.

상태워드의 값이 3830 이다

0011 1000 0011 0000

라인10의 곱셈명령으로 스택의 최소값을 벗어난 값이 나타나면서 UE가 1로 변했고 그 값은 0 이다. OE때는 ∞ 였다.

이제  UE의 마스크를 벗기고 UE을 발생시켜보면 어떤 일이 일어날까?

OE때와 거의 유사하다.

목적지가 메모리일때는 목적지의 값을 훼손하지 않고 예외처리루틴으로 분기한다. OE 때와 똑같다.

목적지가 스택일때는 24576을 지수값에 더해서 ( OE때는 뺐었다 ) 그 값을 크게 한뒤 목적지에 집어넣는다. 크게 한 값이 여전히 최소값보다 작으면, 즉 언더플로우영역안에 있으면 0을 반환한다. OE때와 똑같은 개념이다. 

제어워드와 상태워드의 값이 각각 036f, b8b0 이다

0000 0011 0110 1111 (UM=0)
1011 1000 1011 0000 (Busy,ES,UE)

상태워드를 보면 예외처리루틴으로 갔다는 걸 알 수 있다.
그리고 결과값이 st(0)에 있는데 예상대로 큰수가 나타났다. 바이어스값이 더해진 결과다. 


  • PE (Precision Exception) 와 PM(Precision Mask)

상태워드와 제어워드의 여섯번째 (b5) 비트이다.

지금까지의 예제에서 PE비트가 1로 변한 경우가 몇몇 있었지만 애써 무시한 비트이다.

계산의 결과가 부정확할 때 이 예외가 발생한다. Inexact Result 에 의한 예외라고도 부른다.

계산 결과가 부정확하다 함은자리수를 맞추기 위해 자리올림이나 내림이 발생했을때를 말하며 그때 PE가 1로 변한다. 자리올림이 발생하면 상태워드의 b9인 c1비트가 1로 변하고 자리버림이 발생하면 0 으로 변한다. 이러한 용도로 쓰일때의 이 c1비트는 round up 비트라고 부른다. 자리올림이나 내림은 현재의 rc 값에 따른다. 끝이 없이 이어지는 수가 스택에서 메모리의 싱글이나 더블로 갈때는 언제나 이 PE가 1로 변한다. 아주 흔하게 일어나는 예외라고 할 수 있다. 나눗셈의 결과가 끝도 없이 이어지는 경우, 즉 1/3 = 0.3333... 이러할때도 PE 가 1로 변한다.
1)파이값으로 실험.
상태워드의 값이 3a20 이다. 메모리에 저장된 dNum의 값이 원래값보다 커져 있는게 보인다. 올림이 일어난것이다.

0011 1010 0010 0000
b5인 PE 가 1로 변해있고 b9인 c1 이 1이다. 올림이 발생한걸 표시한다

2) 나눗셈으로 실험
1/3 을 했다. 상태워드의 값이 3820 이다

0011 1000 0010 0000

PE 가 1 이고 c1 이 0 이다. 버림이 발생한 모양이다.

이런건 예상하기 쉽다. 그런데 1/10 같은건 좀 어렵다. 
0.1로 딱 떨어지는 값인데 이진수 나눗셈에서는 그렇지도 않은모양이다. 이것도 PE가 발생한다. 십진수 0.1이 이진수로 표현될때 딱 떨어지지 않고 무한히 계속되기 때문이다. 그래서 어딘가에서 반올림이 일어나게 되고 그래서 PE가 발생한다.


삼각함수 명령, 지수함수 명령, 로그함수 명령은 태생적으로 PE와 함께다. 이들 명령이 쓰이면 늘 PE가 1로 변한다.

스택에 파이값을 넣고 사인함수를 불러보았는데 역시 PE가 발생했고 c1 비트가 1 인걸 보니 자리올림이 발생했다.

지금 살펴본 것은 PE가 독자적으로 발생하기도 하지만 UE 나 OE 등과 같이 딴 예외와 같이 발생하기도 한다.

상태워드를 읽어보면 b5가 1로 되어있는 경우의 예시들이다. 


PE의 마스크가 벗겨져 있을때의 동작은 따로 설명할 것이 없다. 

  • 예외 우선순위

예외가 한꺼번에 여러개 동시에 발생했을때 어떤 예외가 먼저 처리될까?

상태워드의 비트순서랑 비슷하다.

제일 우선 순위가 높은 순서로 배열하면 다음과 같다.

IE ZE DE OE UE PE

IE가 제일 높다. 

예외처리루틴에서 뭔가 할려고 마음먹었다면 이 우선순위를 염두에 두어야한다. 우선순위가 낮은것은 실행이 전혀 안될수도 있다.

fpu는 예외을 언제 알아차릴까?

명령을 실행하기전에 알까 아니면 명령을 실행하고 나서 알까?

정답은 <예외에 따라 다르다> 이다.

1) 명령 실행전에 알 수 있는 예외는 IE, DE, ZE 이고
2) 명령을 실행해 봐야 알 수 예외는 OE, UE, PE 이다

딱 반으로 나뉜다.

마스크가 씌워져 있으면 두 종류의 예외를 구별하기가 어렵지만 마스크를 벗겨 예외가 일어나면 예외처리루틴으로 분기하게 만들면 확인가능하다. 1번의 경우는 명령이 실행되지 않아도 예외가 일어나는걸 알고 있으므로 목적지의 데이타를 건드리지 않는다. 그러나 2번의 경우는 실행해서 목적지에 집어넣어봐야 알 수 있으니까 목적지 데이타에 변화가 일어난다.

여섯개의 예제를 준비했는데 먼저 모든 마스크비트를 다 0 으로 만들었다. 즉 모든 예외의 마스크를 다 벗겼다. 관전포인트는 상태워드에 해당 예외비트가 1로 변해있고 예외처리루틴이 실행중이라는 busy 비트(b15)와 ES비트(b7)가 1로 되어있는지를 확인한다음 목적지의 데이타가 변했는지 변하지 않았는지를 확인하면 된다.

a) IE
sNan을 0 으로 나누었는데 IE 가 1로 변했고 Busy,ES 둘다 1이고 목적지 데이타에 변화없다. 즉 IE는 1번 유형이다.


b) ZE
파이를 0 으로 나누었다. ∞를 반환하지 않았다. 상태워드의 ZE=1, Busy=1, ES=1 이고 목적지 데이타에 변화가 없다. ZE도 1번유형이다.

c) DE
디노멀한 수를 스택에 집어넣고 더해본것 이다. DE, Busy, ES 모두 1이다. 목적지 데이타가 보전되어있다. DE는 1번 유형이다.

d) OE
10의 4000승 되는 아주 큰수를 스택에 넣고 곱해본것이다. 역시 OE가 발생했다. Busy, ES 둘다 1이고 목적지의 데이타가 변했다. OE는 2번 유형이다.

e) UE
10의 마이너스 4000승 되는 아주 작은수를 스택에 넣고 서로 곱했다. 더 작은수가 되어서 언더플로우가 발생했다. UE는 1. 거기에 더해 PE까지 발생했다. busy, ES가 1이고 목적지의 데이타가 변했다. UE는 2번 유형이다.

f) PE
파이값을 qNum으로 보내본것이다.  PE 가 발생했고 Busy, ES 가 1이며 목적지인 qNum의 값이 변했다. 고로 PE는 2번 유형이다.

1 2 3 4 5 6 7 8 9 10 다음