キャッシュで取得しているOpenStreetMapのマップタイルだけを、ローカルにダウンロードしてみる件

2022年8月11日

OpenStreetMapを使って、シミュレータを作成していますが、昨今のセキュリティ事故などを鑑みて、インターネットへのアクセスが一切禁じられた状態での使用を想定しておかなければなりません。

という訳で、OpenStreetMapから地図をバラバラにした画像ファイル(タイル)のダウンロードを試みているのですが、適当にダウンロードすると、不要なタイルまでダウンロードして、

(1)OpenStreetMapのサーバに負荷を与える(という公的な理由)こと

(2)ローカルに不要なファイルが大量に残る(という私的な理由)こと

が、なんとも気にいりません。

ならば「キャッシュに残っているタイルだけをダウロードすればよくね?」と思い(足りなくい部分が見つかったら、その部分だけ再度ダウンロードすればいい)、検討を開始しました。

先ず、キャッシュされている情報を確認してみました。

展開すると、こんな感じ

さて、キャッシュされている、タイルのダンロード先を見つける方法が必要でした。

これは、chrome_cache_view というツールを使うことで解決できそうであることが分かりました。

↓をクリックすると、画面に飛びます。

で、ここからダウンロードします。

解凍して、exeファイルをクリックします。

こんな感じでタイルサーバの位置とファイル名が分かります。

で、tile.osm.org の部分のファイルだけを取り出せればよいのですが、(今のところ、私には、見つけられていません)ので、"Edit" → "Select All" で全部のテキストを取り出して、適当にファイルをぶった切って、取り出すことにしました (良いやり方があったら、私に教えて下さい)。

# ちなみに、この取り出し方は、皆さんの方で好きなようにやって下さい(私は、grepと、emacsを使って手動で切り出すことにしました)。

私の場合、まず、"Edit" → "Select All" で全部のテキストをdummy.txtという名前で保存して、

grep tile.osm.org dummy.txt > dummy2.txt

として

になっている状態で、emacsのkill-rectangle で両端を切り落しました。

さて、ここから問題なのですが、OpenStreetMapの格納方法は、なんでもダウンロードすれば良い、というものではなく、ルールがあります。

/* map の表示準備 */
const map = L.map("map", {
    attributionControl: false,
    zoomControl: false
}).setView(CENTER_LATLNG, 14);
if (USE_OFFLINE_MAP) {
    L.tileLayer('images/map-yamaguchi/{z}/{x}/{y}.png', {
        detectRetina: true,
        minZoom: 13,
        maxZoom: 15,
    }).addTo(map);
}
else {
    L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
        detectRetina: true,
        maxNativeZoom: 18
    }).addTo(map);
}
つまり

https://c.tile.osm.org/18/232959/102413.png
の場合、

"18"というディレクトリを作成し、さらにその中に"232959"というサブディレクトリを作成し、その中に"102313.png"というファイルを配置させる必要があるのです。

これを実施するプログラムをGolangで作りました。

/*
	main13.go

	キャッシュで取り込まれているOpenStreetMapのタイル画像(png)を、ローカルに取り込んで、
	ネットに繋がれていない状況でも、OpenStreetMapを使えるようにする

	前提
	http://www.nirsoft.net/utils/chrome_cache_view.html からChromeCacheView をダウンロードして、
	"https://b.tile.osm.org/13/7284/3196.png"などを取得しておくこと
*/

package main

import (
	"io"
	"net/http"
	"os"
	"strings"

	_ "github.com/lib/pq"
)

func main() {

	var urls = [...]string{
		"https://b.tile.osm.org/13/7284/3196.png",
		"https://c.tile.osm.org/18/232959/102413.png"} // ここに取得したいURLを記載する

	for _, url := range urls {

		arr1 := strings.Split(url, "/")

		//fmt.Println(arr1[3]) // 確認用
		//fmt.Println(arr1[4]) // 確認用
		//fmt.Println(arr1[5]) // 確認用

		os.Mkdir(arr1[3], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[3])       // カレントディレクトリを移動する
		os.Mkdir(arr1[4], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[4])

		response, err := http.Get(url)
		if err != nil { // カレントディレクトリを移動する
			panic(err)
		}
		defer response.Body.Close()

		file, err := os.Create(arr1[5])
		if err != nil {
			panic(err)
		}
		defer file.Close()

		io.Copy(file, response.Body) // ここでダウンロードしたファイルをセーブ

		err = os.Chdir("../..") // ディレクトリを元の位置に戻す(2つ上がる)
		if err != nil {
			panic(err)
		}
	}
}

こんな感じで、上手く動いているようです。

上手く動いていません。

 

ダウンロードした全部のファイルに、

Access denied. See https://operations.osmfoundation.org/policies/tiles/

というテキストが書かてているファイルがダウンロードされています。

どうも、以下の問題みたいです。

Technical Usage Requirements

  • Valid HTTP User-Agent identifying application. Faking another app’s User-Agent WILL get you blocked. Using a library’s default User-Agent is NOT recommended. If a device automatically sends an X-Requested-With header with an application specific Application ID, this will be considered an acceptable substitute for the HTTP User-Agent, although we still recommend setting a valid HTTP User-Agent for the application.
  • When coming from a web page, a valid HTTP Referer. Apps generally do not have a HTTP referer.
  • DO NOT send no-cache headers. (“Cache-Control: no-cache”, “Pragma: no-cache” etc.)
  • Cache Tile downloads locally according to HTTP Expiry Header, alternatively a minimum of 7 days.
  • Maximum of 2 download connections. (Unmodified web browsers’ download limits are acceptable.)

技術的な使用条件
アプリケーションを識別する有効なHTTP User-Agent。他のアプリケーションのUser-Agentを偽装すると、ブロックされる可能性があります。ライブラリのデフォルトのUser-Agentを使用することは推奨されません。デバイスがアプリケーション固有の Application ID を持つ X-Requested-With ヘッダを自動的に送信する場合、これは HTTP User-Agent の代用として認められますが、アプリケーションに対して有効な HTTP User-Agent を設定することを推奨します。
ウェブページからアクセスする場合は、有効なHTTP Refererを指定します。アプリは一般的にHTTP Refererを持ちません。
no-cacheヘッダを送信しないでください。("Cache-Control: no-cache", "Pragma: no-cache" など)
HTTP Expiry Headerに従ってTileダウンロードをローカルにキャッシュします。
ダウンロード接続は最大2回まで。(修正されていないウェブブラウザのダウンロード制限も許容されます)。
注:標準的な設定の最近のウェブブラウザは、一般に上記の技術的要件をすべてクリアしています。

今、対策中です。暫くお待ち下さい。

以下のように変更したら動きました。


/*
	main14.go

	キャッシュで取り込まれているOpenStreetMapのタイル画像(png)を、ローカルに取り込んで、
	ネットに繋がれていない状況でも、OpenStreetMapを使えるようにする

	前提
	http://www.nirsoft.net/utils/chrome_cache_view.html からChromeCacheView をダウンロードして、
	"https://b.tile.osm.org/13/7284/3196.png"などを取得しておくこと
*/

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"

	_ "github.com/lib/pq"
)

func main() {

	var urls = [...]string{
		"https://c.tile.osm.org/18/232959/102413.png",
		"https://b.tile.osm.org/18/232955/102413.png",
		"https://c.tile.osm.org/18/232959/102413.png"} // ここに取得したいURLを記載する

	for _, url := range urls {

		arr1 := strings.Split(url, "/")

		//fmt.Println(arr1[3]) // 確認用
		//fmt.Println(arr1[4]) // 確認用
		//fmt.Println(arr1[5]) // 確認用

		os.Mkdir(arr1[3], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[3])       // カレントディレクトリを移動する
		os.Mkdir(arr1[4], 0777) // ディレクトリを掘る(すでに掘っていてもOKみたい)
		os.Chdir(arr1[4])

		/*  削除
		response, err := http.Get(url)
		if err != nil { // カレントディレクトリを移動する
			panic(err)
		}
		defer response.Body.Close()
		*/

		// 追加(ここから)
		client := &http.Client{}

		req, err := http.NewRequest("GET", url, nil)
		if err != nil {
			fmt.Println(err)
			return
		}
		req.Header.Set("User-Agent", "super-go-client")
		// 追加(ここまで)

		file, err := os.Create(arr1[5])
		if err != nil {
			panic(err)
		}
		defer file.Close()

		// 追加(ここから)
		r, _ := client.Do(req)
		defer r.Body.Close()
		// 追加(ここまで)

		/*
			_, err = io.Copy(file, response.Body) // ここでダウンロードしたファイルをセーブ
		*/

		// 追加(ここから)
		_, err = io.Copy(file, r.Body)
		// 追加(ここまで)
		if err != nil {
			panic(err)
		}

		err = os.Chdir("../..") // ディレクトリを元の位置に戻す(2つ上がる)
		if err != nil {
			panic(err)
		}
	}
}

これで、画像ファイルとして取り出せることが確認できました。

req.Header.Set("User-Agent", "super-go-client")

がポイントだったようです。

予想通り、キャッシュのないところが、欠けています。これは、運用して直していけばいいので、そのうち直します。

ちなみに、index.htmlの方は、

var map = L.map("map", {
			attributionControl: false,
			zoomControl: false
		}).setView(new L.LatLng(36.56240644, 139.9501693), 14); // 宇都宮

		//L.tileLayer('http://localhost:8080/static/{z}/{x}/{y}.png',{
		L.tileLayer('static/{z}/{x}/{y}.png',{
        	detectRetina: true,
        	//minZoom: 13,
        	//maxZoom: 15,
			maxNativeZoom: 18
    	}).addTo(map);


		//L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
		//	detectRetina: true,
		//	maxNativeZoom: 18
		//}).addTo(map);

のように、

L.tileLayer('http://localhost:8080/static/{z}/{x}/{y}.png',{

でも、

L.tileLayer('static/{z}/{x}/{y}.png',{

でも、動作するようです。

2022年8月11日2022/08,江端さんの技術メモ

Posted by ebata