네이버 새지도로 mbtiles 파일만들기 여행

네이버지도가 새롭게 바뀌었는데  

타일맵주소 관점에서는 구글맵과 같아졌다. 

그래서 예전의 방식처럼 여러단계를 거쳐서 mbtiles를 만들 필요가 없다.

자바을 깔고 mobac 만 설치하면 됨

01. 자바설치 
02. mobac 설치 (2019.11.21 현재 2.1.2)
    Mobile Atlas Creator 2.1.2.zip 을 받아와서 압축를 풀기만 하면 됩

    a. Mobile Atlas Creator.exe 을 실행한다.
    b. 창이 하나 뜨는데 
    지도이름을 적당한 이름으로 주고 (여기서는 Jeju) 지도형식은 MBTiles SQLite를 선택한다. 

    c. 아래와 같은 창이 나타난다.

    기본지도로 OpenStreetMap 4UMaps.eu가 나타나는데 map source의 여러항목중에 네이버지도는 없다. 여기에 네이버지도를 넣어보자

03. mobac을 종료한뒤 크롬에서 네이버 새지도를 띄운다. f12를 누르면 화면이 둘로 갈라진다. 아래그림처럼 Network를 선택하고 filter로 img를 선택한뒤 왼쪽의 지도화면을 이러저리 움직여보면 Name항목에 그림파일들이 나타나는데 임의로 하나를 선택하면 아래와 같은 주소를 얻을 수 있다.



https://map.pstatic.net/nrb/styles/basic/1573549764/12/3488/1643.png?mt=bg.ol.sw

위주소에서 png까지의 주소만 추출하여 크롬에서 띄워보면 아래과 같은 그림이 된다.


위 주소에서 https://map.pstatic.net/nrb/styles/basic/1573549764/ 까지는 고정된 값이고 (아마 이 주소도 유동적 일 수도 있다) 나머지 세개의 값인 12, 3488, 1643 은 지도의 위치와 줌레벨에 따라 바뀌는 값이다. 12는 줌레벨, 3488은 x축, 1643은 y축을 나타낸다.

04. 아래와 같은 내용을 담은 xml 파일을 만든다. 이름은 적당한거로 준다. 여기서는 naver.xml 

<?xml version="1.0" encoding="UTF-8"?>
<customMapSource>
<name>naver</name>
<minZoom>0</minZoom>
<maxZoom>21</maxZoom>
<tileType>png</tileType>
<tileUpdate>None</tileUpdate>
<url>https://map.pstatic.net/nrb/styles/basic/1573549764/{$z}/{$x}/{$y}.png</url>
<backgroundColor>#000000</backgroundColor>
</customMapSource>

  위 파일을 mapsources 폴더에 둔다

05. mobac을 실행한뒤 map source에서 naver를 선택하면 세계지도가 나타나는데 마우스를 적당히 움직여 제주도가 화면에 나오도록 한다.


   위 제주도 지도에 정확한 위경도가 반영되어 있는지를 확인하기 위해서 적당한 gpx파일을 구해서 (제주도 gpx로 검색) 지도에 올려본다.

    정확하게 매치가 된다.
    
   이제 영역과 줌레벨을 아래그림처럼 선택한다.
 
  선택하고자 하는 사각형의 왼쪽위를 마우스로 클릭한 다음 오른쪽 아래로 드래그하면 선택사각형을 만들 수 있다. 줌레벨을 6에서 16까지 선택하면 지도파일갯수가 23,909개가 된다. atlas content 항목에서 add selection을 선택하면 위 그림처럼 Layer항목이 추가된다. 이렇게 해놓고 Create Atlas를 누르면 mbtiles 파일이 만들어진다.  

약 1시간 가량 걸린다.

atlas 폴더에 jeju.mbtiles파일이 만들어졌는데 약 100메가의 크기다.

06 끝


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

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

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

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

여하튼 ..
뚜껑을 열면 구리판같은게 보이는데 그곳에 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로 한다. 이처럼 원하는 지역과 줌레벨을 위 프로그램에 적는다.

파이썬(2.7.x 버전)이 이미 설치되어있다고 치고 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파일을 열어서 읽어보자!
그러면 테마렌더링에 관해 더 깊게 이해할 수 있다. 


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