2022/08,江端さんの技術メモ

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/08,江端さんの技術メモ

アイコン↓

csvファイル"fes.csv"↓ (index.htmlと同じディレクトリに置いています)

1, 35.59609497295185, 139.47514321636098
2, 35.593735148646594, 139.46728972468222
3, 35.597968040183645, 139.47051770314877

対応部分のJavaScript(私は、index.htmlに埋め込んでいます)

// 複数のイベント会場をcsvファイルから読み取って作ってみる
		//
		// "fes.csv"の内容
		// 1, 35.59609497295185, 139.47514321636098
		// 2, 35.593735148646594, 139.46728972468222
		// 3, 35.597968040183645, 139.47051770314877
		// 

		fes_icon = L.icon({iconUrl:'https://192.168.0.8:8080/static/fes.png',
        //iconSize: [36, 60], iconAnchor: [18, 60], popupAnchor:[0,-60]});
        iconSize: [60, 36], iconAnchor: [30, 18]});		

	 	// 以下のような書き方ができるらしい(lat, lon)の座標にマーカーを作り,icon情報を与え,ポップアップメッセージを追加する
 		//L.marker( [ lat, lon],{ icon:quad_x_Icon } ).addTo( mymap ).bindPopup( drone_popmessage );

		// CSVファイルを文字列として取得
		let srt = new XMLHttpRequest();
		srt.open("GET", 'fes.csv', false);

		try {
			srt.send(null);
		} catch (err) {
			console.log(err)
		}

		// 配列を用意
		let csletr = [];

		// 改行ごとに配列化
		let lines = srt.responseText.split(/\r\n|\n/);

		// 表示
		console.log(lines)

		// 1行ごとに処理
		for (let i = 0; i < lines.length; ++i) {
			let cells = lines[i].split(",");
			if (cells.length != 1) {
				csletr.push(cells);
			}
		}

		// (試しの)表示
		console.log(csletr)
		console.log(csletr[0][0]) // "test1"
		console.log(csletr[2][1]) // "2"

		// var _lat = 35.59609497295185;
		// var _lon = 139.47514321636098;		// 広袴

		for (let i = 0; i < lines.length; ++i) {		
			var _lat = csletr[i][1]  
			var _lon = csletr[i][2]

	        var fes_marker = L.marker(
    	        [_lat, _lon], 
        	    { popup: 'Origin', draggable: true,  opacity:1.0, icon:fes_icon}
        	).addTo(map);
		}

表示結果↓

以上

 

2022/08,江端さんの技術メモ

WP Githuber MD を有効化する

# 私は、クラッシックエディタとの共存に失敗している為

以上

2022/08,江端さんの技術メモ

WordPressの、クラッシックモードの、「ビジュアル」「テキスト」が表示されなくなった時には、
(以下は見えている状態)

が見えなくなった時は、以下の「ビジュアルリッチエディターを使用しない」のチェックを外す。

以上

2022/08,江端さんの技術メモ

以下のコードは、javascript csvファイルを読み込んで配列化するから丸ごとコピペさせて頂いたものです。

不要なエラー処理やら、ゴチャゴチャしたメソッドとかがなく、実に分かりやすい、素晴しいサンプルコードです。

私の為にコピペさせて頂きました。

<!-----
Javascriptから、csvファイルを読み込む方法

test.csv の中身
test1,test2,test3
a,b,c
1,2,3
----->
<!doctype html>
<html lang="ja">
<head>
    <script>
        // CSVファイルを文字列として取得
        let srt = new XMLHttpRequest();

        srt.open("GET", 'test.csv', false);

        try {
            srt.send(null);
        } catch (err) {
            console.log(err)
        }

        // 配列を用意
        let csletr = [];

        // 改行ごとに配列化
        let lines = srt.responseText.split(/\r\n|\n/);

        // 表示
        console.log(lines)

        // 1行ごとに処理
        for (let i = 0; i < lines.length; ++i) {
            let cells = lines[i].split(",");
            if (cells.length != 1) {
                csletr.push(cells);
            }
        }

        // 表示
        console.log(csletr)
        console.log(csletr[0][0]) // "test1"
        console.log(csletr[2][1]) // "2"
    </script>
</head>
<body>

</body>
</html>

だから、今一度、言おう。『インターネット、ばんざい』

 

2022/08,江端さんの技術メモ

出典 https://www.gixo.jp/blog/327/

最近、よく使われる「アウトカム」という言葉の意味をちょくちょく忘れるので、上記出典よりメモとして画像のみをリンクさせて頂きました。

2022/08,2022/08,江端さんの忘備録

コンプライアンスが叫ばれる昨今です。

Now "Compliance" is more and more important in our society.

今日、Twitterをボンヤリ眺めていたら

Today, when I had seeing Twitter's TL vaguely, I read the following line,

=====

『全然勉強なんかやってないよ」というつよつよエンジニアは、土日もバリバリコード書いてるのにそのことを勉強ではなく「遊び」とか「趣味」とか表現するから、駆け出しエンジニアの人は騙されないでください。

At the time a very strong engineer says "I'm not studying at all,", he/she writes code on weekends as well. They describe it as "fun" or "hobby" instead of studying. Please don't be fooled if you are a novice engineer.

=====

と記載されていて、心底『ドキッ!』としました。

I was really thrilled.

-----

ちなみに、私、今年のゴールデンウイークの9連休の全日、毎日12時間以上、コーディングしていました。

By the way, I was coding for more than 12 hours every day during the entire 9-day Golden Week holiday this year.

しかし、これは、『「遊び」とか「趣味」』です。

However, this is a '"fun" or "hobby".

『楽しかった』から、そう定義して良いと思い込んでいます。

I think that there is no problem to feel fun to do it.

でもって、現在、このコードによって私の頭の中に蓄積された『ノウハウ』は、現在、3つの案件に展開中です。

In addition, the "know-how" accumulated in my mind by this code is currently being deployed in three projects.

-----

私は、このことを公に言わないようにしています(時々、内々では言います)。

I am careful not to say this publicly (but privately).

コンプライアンス的に問題となる蓋然性が高いからです。

This might be occured as complience problem with high probability.

ですので、原則として、沈黙を続けています。

So, principle, I keep it silence.

特に若いエンジニアには、語らないようにしています。

Especially to an youth engineers.

『だって、江端さんが・・・』などと言われたら、私のキャリアは、最終フェーズで崩壊です。

My career would collapse in the final phase if someone said to others, 'Because, Ebata-san...' and so on.