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

業務連絡が、転送メールで送られてきます。

Sometimes, business orders come to us by forwarded mail.

普通に、サブジェクトの記載をコピペして、

Many people are support to copy and paste the message of mail subject, like

「今期、明細書執筆予定者への注意点を展開します」

"Please remained to people who plan to write patent application at this quarter"

とだけ記載して転載されてくるメールが多いです。

and they just forward the mail to us.

―― で、これらのメールは、ほとんどの人間にスルーされる、と

"As a result, these mails will be ignored by most readers"

-----

分かります。面倒くさいですからね。

I can understand it, because they are troublesome.

しかも、管理部門のメールは、『悪意でもあるのか』と思えるほど、文面が分かりにくです。

In addition, the mails from management departments are difficult to understand, as if they are malice.

「何をして欲しいのか、さっぱり分からん」という内容です。

The contents are "I don't know at all what on earth do they expect?"

まあ、管理部門の立場としては、そういう文面にならざるを得ないのも分かります。

Well, I might understand that they have to make the contents like that.

恣意的な内容を記載すると、業務内容を誤解されて、後でつっこまれる恐れがあるからです(面倒ごとになる)。

If they try to write the contents on their subjection, it might make the readers trouble.

-----

私も、面倒なので、基本的にはコピペ&スルーをしますが、例外があります。

I also copy, paste and forward the mail to avoid the troublesome, however I have an exception.

『これは、後で、自分に面倒が振りかかってくるな』と直感したものに関してだけは、丁寧に解説をします。

If I feel that "if I do that, another trouble comes to me", I will explain it by my words carefully.

概要を、江端の文言で解説し、該当者をby nameで指定し、締切の日時を強調して記載します。

I am going to write outline of the mail and explain it by my words and indicate the person by name, and emphasize the deadline.

こんな感じです。

As follows

====== メール文面例(ここから) ======

====== Example of the mail (From here) ======

知財部が激怒しています。

The IT department is furious.

『何度説明しても、書式を間違えて提出している』とのことです。

The reason is "we submit the patent application with wrong formats, even they have already had to explain it to us again and again".

本件に関しては、AさんとBさんが、この対象者のようです。

About this mail, I am afraid that Mr.A and Ms.B are objects.

「今月末までに修正を出せ」と言われています。

We have been ordered to re-submit the modification version.

ご希望があれば、私(江端)の方で、ざっくりレビューします

If you hope. I (ebata) will check it roughly.

レビューを希望しない人のことは知りません。その場合は「自力」で知財部と闘って下さい

I don't care people who don't want it. If so, fight the IT department on your own.

江端

Ebata

====== メール文面例(ここまで) ======

====== Example of the mail (To here) ======

まあ、こういう風に書いておけば、ほぼ100%の対応を得られます。

I know that I can get almost 100% responses, if I write the above.

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

私が知らないだけなのかもしれませんが(知っている人は教えて欲しいでです)、OpenStreetMapの鉄道のデータは、nodeとwayに変換できません ―― というか、変換する方法を、私には知りません(散々探したんだけど、見つからない)。

#でも、絶対にあると思うんだよなぁ。だって、鉄道と車を融合させる研究、腐るほどあるもん。これらの研究が全部、商用のGISシミュレータ使っているとは思えないんだけどな。

ちなみに私は、PostgreSQL + PostGISを使って、GISのシミュレータを作っています。

で、鉄道を路線の上で走らせたいのですが、鉄道の情報は、進行方向の順番通りには表示してくれません。

ですので、一路線を作る為には、geomをLINESTRINGで、座標単位にバラバラにする必要がありますが、問題は、geomが順番通りに表示れない、ということです。

なので、QGISとかを使って、始点・終点を探して、SQLの結果から、順番を探す、という面倒なことをやらざるを得ません。

QGIS弄っていたら、LINESTRINGの座標を得られる方法をたまたま見つけてしまったので、私の為に記録しておきます。

まず、前提はこの内容です。

「OpenStreetMapのデータから鉄道だけを抽出してGeoJSONで出力する方法」を試してみた件

OpenStreetMapのデータからosmiumを使って鉄道だけを抽出しPostGISに入力する方法(宇都宮周辺の鉄道データを使った例)を試してみた件

まず地物表示のボタンを押します。

次に、LRTの路線の最初の部分をクリックします。

次に、(派生した属性)を右クリックして、「地物をコピー」を選択します。

で、これをエディタなどに、コピペすると

wkt_geom tags
LineString (139.89970149999999194 36.55948450000000349, 139.89955630000000042 36.55885789999999957, 139.8995482999999922 36.55877879999999891, 139.89956340000000523 36.55872629999999646, 139.8996118000000024 36.55866100000000074, 139.89968200000001275 36.55861320000000347, 139.8997737999999913 36.55858750000000157, 139.89987840000000574 36.55857249999999681, 139.90136459999999374 36.55845939999999672, 139.90222030000001041 36.55839509999999848, 139.90252409999999372 36.55837619999999788, 139.90541809999999145 36.55817369999999755, 139.90668009999998844 36.55808119999999661, 139.90870390000000612 36.55791560000000118, 139.90887219999999047 36.55789959999999894, 139.90931180000001177 36.55786609999999826)

てな感じで、geomに含まれる座標がでてきます。

これを、出発地から到着地まで、間断なく続ければ、路線の連続の座標が得られる、という訳です(そこそこ面倒ですが、selectのダンプから探すよりはマシです)。

まあ、それぞれのgeomの先頭と終端は、重複する座標となるので、これを消すなどの処理は必要ですが、取り敢えず、これで鉄道の連続する座標は得られます。

この後、これらの座標をDBに放り込むなり、プログラムにベタ書きするなり、煮るなり焼くなり、色々できると思います。

とりあえず、(emacs使い倒して)csvファイルにしました。

ここからpostgreSQLにインポートするのは ―― 多分、なんかの手段があるだろう、と思う。

ここにありました。

[付録B] 地図の上に100人ほど配置してみる(メモ)

私はPostgreSQLをdockerコンテナの中に作っているので、"docker cp"でコンテナ内にcsvファイルを送り込む必要がありました。

utsu_rail_db=# CREATE TABLE "lrt"(
utsu_rail_db(# seq integer,
utsu_rail_db(# x1 float,
utsu_rail_db(# y1 float,
utsu_rail_db(# distance_m float
utsu_rail_db(# );
CREATE TABLE
utsu_rail_db=# \dt
              List of relations
 Schema |      Name       | Type  |  Owner
--------+-----------------+-------+----------
 public | lrt             | table | postgres
 public | spatial_ref_sys | table | postgres
 public | utunomiya       | table | postgres
(3 rows)
utsu_rail_db=# \copy lrt from '/tmp/lrt.csv' with csv
COPY 286
utsu_rail_db=# select * from lrt;
 seq |     x1      |     y1      | distance_m
-----+-------------+-------------+-------------
   1 | 139.8997015 |  36.5594845 |           0
   2 | 139.8995563 |  36.5588579 | 55.68730112
   3 | 139.8995483 |  36.5587788 | 6.785051714
   4 | 139.8995634 |  36.5587263 | 4.769494948
   5 | 139.8996118 |   36.558661 | 7.734168448
   6 |  139.899682 |  36.5586132 | 8.800865673
   7 | 139.8997738 |  36.5585875 | 10.43856664
   8 | 139.8998784 |  36.5585725 | 11.70055883
285 | 140.0145241 | 36.572845 | 28.22469677
286 | 140.0117433 | 36.57920089 | 623.5656217
(286 rows)
以上

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

どうせ、また、いずれ、同じ内容で探し回ることになると思うので、今、記載しておきます

utsu_db=# SELECT seq, node, edge, cost, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length as cost FROM
ways',100, 600, false);
seq | node | edge | cost | agg_cost
-----+-------+-------+------------------------+-----------------------
1 | 100 | 41 | 0.0002983579226385357 | 0
2 | 18996 | 21479 | 0.0006788280602488493 | 0.0002983579226385357
3 | 6119 | 24518 | 0.00019027162164538517 | 0.000977185982887385
4 | 19331 | 6766 | 0.0010928248868793096 | 0.0011674576045327702
5 | 18976 | 21474 | 0.0003135978634977571 | 0.00226028249141208
6 | 6120 | 24519 | 0.00014212958875406848 | 0.0025738803549098374
7 | 2595 | 29737 | 0.00012339732574262452 | 0.002716009943663906
8 | 6137 | 30391 | 0.00016648675622455585 | 0.0028394072694065305

このcostの単位って何なの? 私は距離が知りたいだけなんだけど? と探し回ったら、なんのことはない、単に"length"→"length_m"にすればよかっただけでした。

utsu_db=# SELECT seq, node, edge, cost, agg_cost FROM pgr_dijkstra('SELECT gid as id, source, target, length_m as cost FROM ways',100, 600, false);
seq | node | edge | cost | agg_cost
-----+-------+-------+--------------------+--------------------
1 | 100 | 41 | 27.7006591508524 | 0
2 | 18996 | 21479 | 63.36986127215735 | 27.7006591508524
3 | 6119 | 24518 | 17.68514641657604 | 91.07052042300975
4 | 19331 | 6766 | 102.22241800670713 | 108.75566683958579
5 | 18976 | 21474 | 30.189796472672 | 210.97808484629292
6 | 6120 | 24519 | 14.342809731736027 | 241.16788131896493
7 | 2595 | 29737 | 13.438709335725413 | 255.51069105070096

ちなみに、agg_costは、距離の足し算の合計値を出してくれるので、便利です。

いちおう、GoogleマップとQGISつかって、大体の距離も合っていたので、大丈夫でしょう。

 

 

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

// go get github.com/lib/pq を忘れずに
// go run main6.go

/*
	前提は、https://ebata-books.booth.pm/items/3351408 の環境

	(1)スタート地点  139.91804656152837 36.57246810341353,
	(2)ゴール地点 139.93515104919825 36.55883778950532
	(3)とした時、この2点に最も近いノードを探し出して、
	(4)その2点をダイクストラ法で、最短距離探索をするプログラム

	分かったこと
	上記(3)のノードが、孤立ノードになっている場合があり、この場合(4)が実施できないので、
	ダイクストラに失敗する場合は、別のノードでトライアルする必要あり 
	(このコードでは展開しないけど)
*/

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

var source int
var longitude float64
var latitude float64
var dist float64

func main() {
	db, err := sql.Open("postgres",
		"user=postgres password=password host=localhost port=15432 dbname=utsu_db sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	// スタート地点  139.91804656152837 36.57246810341353
	rows, err := db.Query("SELECT source, x1 as longitude, y1 as latitude, ST_Distance('SRID=4326;POINT(139.91804656152837 36.57246810341353)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(139.91804656152837 36.57246810341353)'), 300.0) ORDER BY dist LIMIT 1")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		fmt.Println(source, longitude, latitude, dist)
	}

	o_source := source

	// ゴール地点 139.93515104919825 36.55883778950532
	rows, err = db.Query("SELECT source, x1 as longitude, y1 as latitude, ST_Distance('SRID=4326;POINT(139.93515104919825 36.55883778950532)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(139.93515104919825 36.55883778950532)'), 300.0) ORDER BY dist LIMIT 1")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		fmt.Println(source, longitude, latitude, dist)
	}

	d_source := source

	fmt.Println(o_source, d_source)

	// これが基本形
	// rows, err = db.Query("SELECT seq, node, edge, cost FROM pgr_dijkstra('SELECT gid as id, source, target,length as cost FROM ways',8889, 22848, false)")

	// この bigint なるものは、https://wp.kobore.net/%E6%B1%9F%E7%AB%AF%E3%81%95%E3%82%93%E3%81%AE%E6%8A%80%E8%A1%93%E3%83%A1%E3%83%A2/error-function-pgr_dijkstraunknown-start_vid-bigint-end_vid-bigint-directed-boolean-does-not-exist-line-7-pgr_dijkstra-hint-no-function-matches-the-given-name/ に記載がある
	rows, err = db.Query("SELECT seq, node, edge, cost FROM pgr_dijkstra('SELECT gid as id, source, target,length as cost FROM ways', $1::bigint , $2::bigint , false)", o_source, d_source)

	// こっちも稼動します
	// str := "SELECT seq, node, edge, cost FROM pgr_dijkstra('SELECT gid as id, source, target,length as cost FROM ways'," + strconv.Itoa(o_source) + "," + strconv.Itoa(d_source) + ", false)"
	// fmt.Println(str)
	// rows, err = db.Query(str)

	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		var seq int
		var node int
		var edge int
		var cost float64

		if err := rows.Scan(&seq, &node, &edge, &cost); err != nil {
			fmt.Println(err)
		}
		fmt.Println(seq, node, edge, cost)
	}

	if err := db.Ping(); err != nil {
		log.Fatal("PingError: ", err)
	}
}

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

// go get github.com/lib/pq を忘れずに
// go run main5.go
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "user=postgres password=password host=localhost port=15432 dbname=utsu_db sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	rows, err := db.Query("SELECT gid,tag_id,x1 FROM ways")
	if err != nil {
		log.Fatal(err)
	}

	// 139.9182893339256, 36.573831584767085

	defer rows.Close()

	for rows.Next() {
		var gid int
		var tag_id int
		var x1 float64
		if err := rows.Scan(&gid, &tag_id, &x1); err != nil {
			fmt.Println(err)
		}
		fmt.Println(gid, tag_id, x1)
	}

	if err := db.Ping(); err != nil {
		log.Fatal("PingError: ", err)
	}
}

さて

http://www.kobore.net/diary_techno/?date=20181101

の中の、点(139.9182893339256 36.573831584767085)から半径300メートル以内の全部のノードを、近い順に出せ

を、psqlで出力させると、こんな感じになります。

Golangで実施するとなると、こんなコードになります(select文のコピペで足ります)。

// go get github.com/lib/pq を忘れずに
// go run main5.go
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres",
		"user=postgres password=password host=localhost port=15432 dbname=utsu_db sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	rows, err := db.Query("SELECT source, x1 as longitude, y1 as latitude, ST_Distance('SRID=4326;POINT(139.9182893339256 36.573831584767085)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(139.9182893339256 36.573831584767085)'), 300.0) ORDER BY dist")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {
		var source int
		var longitude float64
		var latitude float64
		var dist float64

		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		fmt.Println(source, longitude, latitude, dist)
	}

	if err := db.Ping(); err != nil {
		log.Fatal("PingError: ", err)
	}
}

出力結果は同じものになります。

 

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

OpenStreetMapのデータからosmiumを使って鉄道だけを抽出しPostGISに入力する方法(宇都宮周辺の鉄道データを使った例)

を、参考にさせて頂いて、試しているのですが、うちのosmiumは、

root@c517fca5dec0:/usr/bin# osmium export -f pg -o utsunomiya-railway-latest.pg utsunomiya-railway-latest.osm.pbf
Set output format with --output-format or -f to 'geojson', 'geojsonseq', or 'text'.

といって、pgファイルを作ってくれません。色々調べたのですが、どうにも上手くいかずに、別の方法を探しています。

で、ちょっと興味本位で、

root@c517fca5dec0:/usr/bin# apt install osm2pgsql

をやってみたら、コンテナの中に、サクッとインストールされたので、これを使う方向で検討しています。

------

>docker container exec -it utsunomiya_db_1 bash

で入ったのですが、osm2pgsqlとかosmiumはインストールできなかったのですが、apt update, apt install, apt upgrade, などを叩き込んで、

>apt install osm2pgsql

>apt install osmium-tool  (×osmium)

>apt install postgresql

を強行しました。

こうしたら、上記のエラー(Set output format with --output-format or -f to 'geojson', 'geojsonseq', or 'text'.)が見えなくなりましたので、osm2pgsqlの方向は見合わせることにしました

よし、続行だ。

この後、アップデートすると動かなくなるので、アップデートを中途半端に行って(完璧にしない)で、pgファイルを作って、インストールに成功。

その後、上記参照ページのSさんから教えて頂いた

utsu_rail_db=# select geom, tags->>'railway' as railway, tags->>'name' as name from utunomiya where tags->>'name' like '%宇都宮ライトレール';

を実施しました。

| construction | (仮称)宇都宮ライトレール
0102000020E6100000070000000725CCB43D7D61408EA2186A5E474240E9CD4D40387D61406409C61C5F474240C6490625317D61407055230560474240197DBBDB2B7D6140A5AA645B61474240BB1171CE237D614050BA3EBD63474240970E8C721F7D6140A95B2CFB644742
4055FBCFF5187D6140B3171B0467474240

| construction | (仮称)宇都宮ライトレール
0102000020E610000008000000BE5B0F15197D61400686072868474240B3075A81217D6140BA50549165474240419B1C3E297D6140B53D30366347424063AD461B2C7D6140685E697462474240CC7C073F317D6140638DC4156147424009E29755337D6140ECB886BE604742
405A2A6F47387D6140E7CD3C146047424069520ABA3D7D6140B130444E5F474240

| construction | (仮称)宇都宮ライトレール
(74 rows)

と、出てきました。

ーーーようやく、宇都宮GISデータベース目処が付きました。

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

「OpenStreetMapのデータから鉄道だけを抽出してGeoJSONで出力する方法」を試してみた件

を前提として、osmから道路と鉄道路線のみを残して残りは消去する方法を探してみました。

どうやら、これでできるようです。

まず、宇都宮地区を抽出します。

osmium extract --bbox 139.76675736787732,36.47004171587971,140.1596765021647,36.659949299138 -o utunomiya.osm.pbf kanto-latest.osm.pbf

次に、以下を行います。

root@2e317961f1d0:/tmp# osmium tags-filter utunomiya.osm.pbf nw/railway nw/highway=motorway,service,primary,secondary,tertiary,unclassified,residential -o utunomiya-lrt-latest.osm.pbf

上記のコマンドのnw(r)は、以下のような意味になっています。

nwr は、OpenStreetMapの nodewayrelationsを指定しています。

nw/(キー)=(タグ),(タグ),(タグ),(タグ),(タグ).....

という記述をします。

(キー)は、https://taginfo.openstreetmap.org/keys を参照

(タグ)は、https://taginfo.openstreetmap.org/tags を参照するといいでしょう。

https://wiki.openstreetmap.org/wiki/JA:Tag:highway%3Dresidential の、

上記をクリックすると、motorway,service,primary,secondary,tertiary,unclassified,residential 

の意味が分かります(多分)。

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

http://kobore.net/test1031-2.cpp

/////////////////////////////////////////
// 
//
// 適当な2つの座標を指定して、そのルートをトレースする
// 
//  ST_Distance, ST_DWithinの使い方
//



/*

gcc -g -I"D:\PostgreSQL\pg96\include" test1031-2.cpp -o test1031-2.exe -L"D:\PostgreSQL\pg96\lib" -llibpq -lwsock32 -lws2_32

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>
#include <math.h>
#include "libpq-fe.h"

struct PERSON{  
  double p_x; // 現在のX座標
  double p_y; // 現在のY座標
};

#define rad2deg(a) ((a)/M_PI * 180.0) /* rad を deg に換算するマクロ関数 */
#define deg2rad(a) ((a)/180.0 * M_PI) /* deg を rad に換算するマクロ関数 */

double distance_km(double a_longitude, 
				   double a_latitude, 
				   double b_longitude, 
				   double b_latitude, 
				   double *rad_up)				   				 
{
  double earth_r = 6378.137;

  double loRe = deg2rad(b_longitude - a_longitude); // 東西  経度は135度
  double laRe = deg2rad(b_latitude - a_latitude); // 南北  緯度は34度39分

  double EWD = cos(deg2rad(a_latitude))*earth_r*loRe; // 東西距離
  double NSD = earth_r*laRe; //南北距離

  double distance = sqrt(pow(NSD,2)+pow(EWD,2));  
  *rad_up = atan2(NSD, EWD);

  return distance;
}


double diff_longitude(double diff_p_x, double latitude) 
{
  double earth_r = 6378.137;
  // ↓ これが正解だけど、
  double loRe = diff_p_x / earth_r / cos(deg2rad(latitude)); // 東西
  // 面倒なので、これで統一しよう(あまり差が出ないしね)
  //double loRe = diff_p_x / earth_r / cos(deg2rad(35.700759)); // 東西
  double diff_lo = rad2deg(loRe); // 東西

  return diff_lo; // 東西
}
  
double diff_latitude(double diff_p_y) 
{
  double earth_r = 6378.137;
  double laRe = diff_p_y / earth_r;  // 南北
  double diff_la = rad2deg(laRe); // 南北
  
  return diff_la; // 南北
}

int main (){

  PERSON test_person;

  // 適当な2つの座標
  double dep_x = 139.46507, dep_y = 35.59577;
  double arr_x = 139.47627, arr_y = 35.60358;

  const char *conninfo = "host=localhost user=postgres password=c-anemone dbname=city_routing";
  
  // データベースとの接続を確立する 
  PGconn *conn = PQconnectdb(conninfo);
  PGresult *res;

  // バックエンドとの接続確立に成功したかを確認する
  if (PQstatus(conn) != CONNECTION_OK){
	fprintf(stderr, "Connection to database failed: %s",
			PQerrorMessage(conn));
  }
  
  /////////////////////// startのもっとも近いノード番号を検索する ////////////////////////
  
  char stringSQL[1024] = {0};  
  
  // 半径300メートルくらいで検索
  sprintf(stringSQL,"SELECT source, ST_Distance('SRID=4326;POINT(%f %f)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%f %f)'), 300.0) ORDER BY dist;", dep_x, dep_y, dep_x, dep_y);
  
  res = PQexec(conn, stringSQL);
	
  if (res == NULL){
	fprintf(stderr, "failed: %s",
			PQerrorMessage(conn));
  }
  
  // SELECTの場合戻り値は PGRES_TUPLES_OK.  これを確認しておく
  if (PQresultStatus(res) != PGRES_TUPLES_OK){
	PQerrorMessage(conn);
  }
  
  int nFields = PQnfields(res);
  
  // そして行を表示する。
  int i_count = 0;
  
  while( atof(PQgetvalue(res, i_count, 1)) == 0.0){
	i_count += 1;
  }
  
  double dist = atof(PQgetvalue(res, i_count, 1));
  int start_source = atoi(PQgetvalue(res, i_count, 0));
  
  PQclear(res); // SQL文の発行に対して必ずクリアする
  
  /////////////////////// startとstartのもっとも近いノードの距離を計測する ////////////////////////  
  
  memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア

  sprintf(stringSQL,"SELECT ST_Distance('SRID=4326;POINT(%f %f)'::GEOGRAPHY, the_geom) from ways where source = %d;", dep_x, dep_y, start_source );

  res = PQexec(conn, stringSQL);  

  if (res == NULL){
	fprintf(stderr, "failed: %s",
			PQerrorMessage(conn));
  }
  
  // SELECTの場合戻り値は PGRES_TUPLES_OK.  これを確認しておく
  if (PQresultStatus(res) != PGRES_TUPLES_OK){
	PQerrorMessage(conn);
  }
  
  nFields = PQnfields(res);

  double dep_dist = atof(PQgetvalue(res, 0, 0));  

  PQclear(res); // SQL文の発行に対して必ずクリアする


  ////////////////////// endのもっとも近いノード番号を検索する///////////////////////////

  memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア
  
  // 半径300メートルくらいで検索
  sprintf(stringSQL,"SELECT source, ST_Distance('SRID=4326;POINT(%f %f)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(%f %f)'), 300.0) ORDER BY dist;", arr_x, arr_y, arr_x, arr_y);

  res = PQexec(conn, stringSQL);
	
  if (res == NULL){
	fprintf(stderr, "failed: %s",
			PQerrorMessage(conn));
  }
  
  // SELECTの場合戻り値は PGRES_TUPLES_OK.  これを確認しておく
  if (PQresultStatus(res) != PGRES_TUPLES_OK){
	PQerrorMessage(conn);
  }
  
  nFields = PQnfields(res);
  
  // そして行を表示する。
  i_count = 0;
  
  while( atof(PQgetvalue(res, i_count, 1)) == 0.0){
	i_count += 1;
  }
  
  dist = atof(PQgetvalue(res, i_count, 1));
  int end_source = atoi(PQgetvalue(res, i_count, 0));
  
  PQclear(res); // SQL文の発行に対して必ずクリアする

  /////////////////////// endとendのもっとも近いノードの距離を計測する ////////////////////////  
  
  memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア

  sprintf(stringSQL,"SELECT ST_Distance('SRID=4326;POINT(%f %f)'::GEOGRAPHY, the_geom) from ways where source = %d;", arr_x, arr_y, end_source );

  res = PQexec(conn, stringSQL);  

  if (res == NULL){
	fprintf(stderr, "failed: %s",
			PQerrorMessage(conn));
  }
  
  // SELECTの場合戻り値は PGRES_TUPLES_OK.  これを確認しておく
  if (PQresultStatus(res) != PGRES_TUPLES_OK){
	PQerrorMessage(conn);
  }
  
  nFields = PQnfields(res);

  double arr_dist = atof(PQgetvalue(res, 0, 0));  

  PQclear(res); // SQL文の発行に対して必ずクリアする

  printf("start_source:%d end_source:%d\n",start_source, end_source);
  printf("dep_dist:%f arr_dist:%f\n",dep_dist, arr_dist);


  
  ////////////////// start_sourceとend_sourceのノードをダイクストラで計算する //////////////////////

  printf("%-15f,%-15f\n",dep_x, dep_y); // 出発点を書き込んでおく

  memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア

  sprintf(stringSQL, "SELECT node, edge FROM pgr_dijkstra('SELECT gid as id, source, target, cost_s As cost, reverse_cost_s AS reverse_cost FROM ways', %d, %d, true );",start_source, end_source);

  res = PQexec(conn, stringSQL);  

  if (res == NULL){
	fprintf(stderr, "failed: %s",
			PQerrorMessage(conn));
  }
  
  // SELECTの場合戻り値は PGRES_TUPLES_OK.  これを確認しておく
  if (PQresultStatus(res) != PGRES_TUPLES_OK){
	PQerrorMessage(conn);
  }
  
  nFields = PQnfields(res);

  // start_sourceとend_sourceのノードのノード番号の一つをゲットする処理
  for (int i = 0; i < PQntuples(res)-1  ; i++) {  // バス停とバス停の間のノードを全部出す
	  
	/* 
	   ここでは、両端のエッジの座標が必要になる。
	   pgr_dijkstraでは、エッジ(両端)情報(x1,y1,x2,y2)が取得できるのだけど、
	   どっちが先端でどっちが終端かが分からない。
	   
	   そこで(恐しく迂遠だけえど)、まずエッジ情報(x1,y1,x2,y2)を得てから、
	   ノード情報(x1, y1)を取得する(ちなみにノード情報のx2,y2は、
	   交差点などの場合は複数出てくるので、信用してはならない)。
	   
	   で、ノード情報のx1,y1を先端として、エッジ情報のx1,y1とx2,y2と一致していない方を終端とする
	   という処理を取っている。
	   
	   (もっと簡単な方法があったら、誰か教えて下さい)
	   
	*/
	
	double node[2] = {0.0}; // x1, y1
	double edge[4] = {0.0}; // x1, y1, x2, y2
	
	int dummy_int_1 = 0;
	int dummy_int_2 = 0;
	
	for (int j = 0; j < nFields; j++) { // (j=0:node(source)と j=1:edge(gid)の値をゲットする)
	  
	  // まずノードの方
	  if (j == 0){//(j=0:node(source)
		
		memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア
		sprintf(stringSQL, "SELECT x1,y1 from ways where source = %s;",
				PQgetvalue(res, i, j)); // ノードの座標を得る
		
		dummy_int_1 = atof(PQgetvalue(res, i, j));
		
		PGresult *res2 = PQexec(conn, stringSQL);
		if (res2 == NULL){
		  fprintf(stderr, "failed: %s",
				  PQerrorMessage(conn));
		}
		
		
		int nFields2 = PQnfields(res2);
		
		for (int j2 = 0; j2 < nFields2; j2++) { // node(source)のx1,y1の2つの値
		  // printf("%-15s", PQgetvalue(res2, 0, j2));
		  node[j2] = atof(PQgetvalue(res2, 0, j2));  
		}
		
		PQclear(res2); // SQL文の発行に対して必ずクリアする
	  }
	  
	  //次にエッジの方
	  if (j == 1){//(j=1:edge(gid)
		
		memset( stringSQL, 0, sizeof(stringSQL)); // 念の為クリア
		
		sprintf(stringSQL, "SELECT x1,y1,x2,y2 from ways where gid = %s;",
				PQgetvalue(res, i, j)); // ノードの座標を得る
		
		dummy_int_2 = atof(PQgetvalue(res, i, j));
		
		PGresult *res2 = PQexec(conn, stringSQL);
		if (res2 == NULL){
		  fprintf(stderr, "failed: %s",
				  PQerrorMessage(conn));
		}
		
		int nFields2 = PQnfields(res2);
		
		for (int j2 = 0; j2 < nFields2; j2++) { // node(source)のx1,y1の2つの値
		  // printf("%-15s", PQgetvalue(res2, 0, j2));
		  edge[j2] = atof(PQgetvalue(res2, 0, j2));  
		}
		
		PQclear(res2); // SQL文の発行に対して必ずクリアする
		
	  }
	}
	
	double start_x, start_y, end_x, end_y;
	
	//出揃ったところで、始点と終点の判定を行う
	if ((fabs(node[0] - edge[0]) < 1e-6) && (fabs(node[1] - edge[1]) < 1e-6)){
	  start_x = edge[0];
	  start_y = edge[1];
	  end_x = edge[2];
	  end_y = edge[3];
	}else{
	  start_x = edge[2];
	  start_y = edge[3];
	  end_x = edge[0];
	  end_y = edge[1];
	}
	
	// printf("両端の確定 %f,%f,%f,%f\n", start_x, start_y, end_x, end_y);
	
	//両端の進行方向(ベクトル)を計算する
	double rad_up1;
	distance_km(start_x, start_y, end_x, end_y, &rad_up1);
	
	// 確定直後にバス位置は、出発点(start_x, start_y)に強制移動する
	test_person.p_x = start_x;
	test_person.p_y = start_y;
	
	// ここから0.1m単位でグルグル回す

	int do_flag = 0;
	do{
	  // printf("x=%-15f,y=%-15f\n",test_person.p_x, test_person.p_y);
	  printf("%-15f,%-15f\n",test_person.p_x, test_person.p_y);
	  
	  // 以下の0.1は1サンプリング0.1メートルを擬制
	  test_person.p_x += diff_longitude(0.1 * cos(rad_up1), test_person.p_y);	
	  test_person.p_y += diff_latitude(0.1 * sin(rad_up1));
	  
	  double rad0 = atan2((end_y - start_y),(end_x - start_x));
	  double rad1 = atan2((end_y - test_person.p_y),(end_x - test_person.p_x));
	  
	  // ここは、http://kobore.net/over90.jpg で解説してある
	  
	  if (fabs(rad0 - rad1) >= 3.141592654 * 0.5){
		// 終点越えの場合、終点に座標を矯正する
		test_person.p_x = end_x;
		test_person.p_y = end_y;
		
		do_flag = 1; // フラグを上げろ
	  }
	  
	}while(do_flag == 0);
	
  } // バス停とバス停の間のノードを全部出す
  
  PQclear(res); // SQL文の発行に対して必ずクリアする
  
  printf("%-15f,%-15f\n",arr_x, arr_y); // 終着点を書き込んでおく

}

上記のプログラムと同じような振舞をするプログラムをGolangで書いてみました。

// go get github.com/lib/pq を忘れずに
// go run main7.go

/*
	前提は、https://ebata-books.booth.pm/items/3351408 の環境

	(1)スタート地点  139.91804656152837 36.57246810341353,
	(2)ゴール地点 139.93515104919825 36.55883778950532
	(3)とした時、この2点に最も近いノードを探し出して、
	(4)その2点をダイクストラ法で、最短距離探索をするプログラム

	分かったこと
	上記(3)のノードが、孤立ノードになっている場合があり、この場合(4)が実施できないので、
	ダイクストラに失敗する場合は、別のノードでトライアルする必要あり
	(このコードでは展開しないけど)
*/

package main

import (
	"database/sql"
	"fmt"
	"log"
	"math"

	_ "github.com/lib/pq"
)

var source int
var longitude float64
var latitude float64
var dist float64

func rad2deg(a float64) float64 {
	return a / math.Pi * 180.0
}

func deg2rad(a float64) float64 {
	return a / 180.0 * math.Pi
}

func distance_km(a_longitude, a_latitude, b_longitude, b_latitude float64) (float64, float64) {
	earth_r := 6378.137

	loRe := deg2rad(b_longitude - a_longitude) // 東西  経度は135度
	laRe := deg2rad(b_latitude - a_latitude)   // 南北  緯度は34度39分

	EWD := math.Cos(deg2rad(a_latitude)) * earth_r * loRe // 東西距離
	NSD := earth_r * laRe                                 //南北距離

	distance_km := math.Sqrt(math.Pow(NSD, 2) + math.Pow(EWD, 2))
	rad_up := math.Atan2(NSD, EWD)

	return distance_km, rad_up
}

func diff_longitude(diff_p_x, latitude float64) float64 {

	earth_r := 6378.137
	// ↓ これが正解だけど、
	loRe := diff_p_x / earth_r / math.Cos(deg2rad(latitude)) // 東西
	// 面倒なので、これで統一しよう(あまり差が出ないしね)
	//loRe := diff_p_x / earth_r / math.Cos(deg2rad(35.700759)) // 東西
	diff_lo := rad2deg(loRe) // 東西

	return diff_lo // 東西
}

func diff_latitude(diff_p_y float64) float64 {
	earth_r := 6378.137
	laRe := diff_p_y / earth_r // 南北
	diff_la := rad2deg(laRe)   // 南北

	return diff_la // 南北
}

func sub_main() {

	x1, y1 := 139.91804656152837, 36.57246810341353
	x2, y2 := 139.93515104919825, 36.55883778950532

	d_km, rad_up := distance_km(x1, y1, x2, y2)

	//x1 += diff_longitude(0.00566*math.Cos(rad_up), y1)
	//y1 += diff_latitude(0.00566 * math.Sin(rad_up))

	x1 += diff_longitude(1.0*math.Cos(rad_up), y1)
	y1 += diff_latitude(1.0 * math.Sin(rad_up))

	fmt.Println(d_km, rad_up)
	fmt.Println(x1, y1)

	d_km, rad_up = distance_km(x1, y1, x2, y2)

	//x1 += diff_longitude(0.00566*math.Cos(rad_up), y1)
	//y1 += diff_latitude(0.00566 * math.Sin(rad_up))

	x1 += diff_longitude(1.0*math.Cos(rad_up), y1)
	y1 += diff_latitude(1.0 * math.Sin(rad_up))

	fmt.Println(d_km, rad_up)
	fmt.Println(x1, y1)

}

func main() {
	db, err := sql.Open("postgres",
		"user=postgres password=password host=localhost port=15432 dbname=utsu_db sslmode=disable")
	if err != nil {
		log.Fatal("OpenError: ", err)
	}
	defer db.Close()

	// スタート地点  139.91804656152837 36.57246810341353
	rows, err := db.Query("SELECT source, x1 as longitude, y1 as latitude, ST_Distance('SRID=4326;POINT(139.91804656152837 36.57246810341353)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(139.91804656152837 36.57246810341353)'), 300.0) ORDER BY dist LIMIT 1")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		fmt.Println(source, longitude, latitude, dist)
	}

	o_source := source

	// ゴール地点 139.93515104919825 36.55883778950532
	rows, err = db.Query("SELECT source, x1 as longitude, y1 as latitude, ST_Distance('SRID=4326;POINT(139.93515104919825 36.55883778950532)'::GEOGRAPHY, the_geom) as dist FROM ways WHERE ST_DWithin(the_geom, ST_GeographyFromText('SRID=4326;POINT(139.93515104919825 36.55883778950532)'), 300.0) ORDER BY dist LIMIT 1")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	for rows.Next() {

		if err := rows.Scan(&source, &longitude, &latitude, &dist); err != nil {
			fmt.Println(err)
		}
		fmt.Println(source, longitude, latitude, dist)
	}

	d_source := source

	fmt.Println(o_source, d_source)

	//rows, err = db.Query("select x1,y1 from ways where source in (SELECT node as source FROM pgr_dijkstra('SELECT gid as id, source, target,length_m as cost FROM ways', $1::bigint , $2::bigint , false))", o_source, d_source)
	rows, err = db.Query("SELECT node as source FROM pgr_dijkstra('SELECT gid as id, source, target,length_m as cost FROM ways', $1::bigint , $2::bigint , false)", o_source, d_source)
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()

	fmt.Println("2")

	x1, y1 := -1.0, -1.0
	_x1, _y1, _x2, _y2 := -1.0, -1.0, -1.0, -1.0
	px, py := -1.0, -1.0
	flag := 0
	f_flag := 0

	for rows.Next() {

		if err := rows.Scan(&source); err != nil {
			fmt.Println(err)
		}

		rows2, err := db.Query("SELECT source, x1, y1 from ways where source = $1::bigint limit 1", source)
		if err != nil {
			log.Fatal(err)
		}

		for rows2.Next() {

			if f_flag == 0 { //初回だけ2居合入力
				if err := rows2.Scan(&source, &x1, &y1); err != nil {
					fmt.Println(err)
				}
				_x1, _y1 = x1, y1
				//fmt.Println(_x1, _y1)
				f_flag = 1
				continue
			}

			if err := rows2.Scan(&source, &x1, &y1); err != nil {
				fmt.Println(err)
			}
			_x2, _y2 = x1, y1

			//fmt.Println(_x2, ",", _y2)
			_, rad_up := distance_km(_x1, _y1, _x2, _y2)
			//fmt.Println("_x1,_y1,_x2,_y2, rad_up:", _x1, _y1, _x2, _y2, rad_up)

			px, py = _x1, _y1

			for {

				//平均時速20km サンプリング時間は、3.6秒  平均速度は 5.56m 20m/3.6秒
				// 悩むところだな、サンプリング時間を1秒にするか3.6秒にするか → 1秒にするか。
				// 距離端数は切り捨てにして、実質は、平均時速を20km以下としよう
				// (カーブでは減速する、と)

				px += diff_longitude(0.00556*math.Cos(rad_up), py)
				py += diff_latitude(0.00556 * math.Sin(rad_up))

				//double rad0 = atan2((end_y - start_y),(end_x - start_x));
				//double rad1 = atan2((end_y - test_person.p_y),(end_x - test_person.p_x));

				rad0 := math.Atan2((_y2 - _y1), (_x2 - _x1))
				rad1 := math.Atan2((_y2 - py), (_x2 - px))
				// ここは、http://kobore.net/over90.jpg で解説してある

				if math.Abs(rad0-rad1) >= math.Pi*0.5 {
					// 終点越えの場合、終点に座標を矯正する
					px, py = _x2, _y2
					flag = 1 // フラグを上げろ
				}

				fmt.Println(px, ",", py)

				if flag == 1 {
					flag = 0
					_x1, _y1 = _x2, _y2
					break
				}
			}
		}
	}

	//平均時速20km サンプリング時間は、3.6秒  平均速度は 5.56m 20m/3.6秒
	// 悩むところだな、サンプリング時間を1秒にするか3.6秒にするか → 1秒にするか。
	// 距離端数は切り捨てにして、実質は、平均時速を20km以下としよう

	if err := db.Ping(); err != nil {
		log.Fatal("PingError: ", err)
	}
}

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

「OpenStreetMapから鉄道路線データを落とせない」と愚痴っていたら、社内の人から「OSMに鉄道データ入っていますよ」と言われて、愕然とした件

という訳で、今日は、OpenStreetMapX.jl をダウンロードして色々やっていたのですが、よく分からりませんでした。というか、何ができるのか、が今一つ肚に落ちてこないのです。

で、こちらの路線は諦めて、とにかく鉄道データが取れる手段を探しました。

OpenStreetMapのデータから鉄道だけを抽出してGeoJSONで出力する方法

を試すことにしました。
から、osm.pbfファイルを落してきました。

osmium を使う方法がでていたのですが、これが私のWindows10環境では実現できませんでした。(正確に言うと、私の環境には"cl.exe"というコマンドがないので、コンパイルができない。Visual Studio C++(?)を入れるとできるらしいのですが、これ以上、パソコンの環境を汚したくなったのです)。
で、色々試みたのですが(この間5~6時間)、上手くいかず、dockerでないかなにないかなーと探していたら、見付けました。
1つ目のWindowsのコマンドプロンプト(MSYS64では上手く動かない)かったので、
C:\Users\ebata>docker run -t -i stefda/osmium-tool /bin/bash
で、いきなりDockerのコンテナの中に入りました。

/bin , /usr/bin, /usr/local/bin のどこかで当るだろうと探りを入れていたら、

root@e87f198ccc07:/usr/bin# ls o*
objcopy objdump od odbcinst ogdi-config openssl osage osmium
root@e87f198ccc07:/usr/bin# osmium
Usage: osmium COMMAND [ARG...]

/usr/binで当たりました。

で、作業用のディレクトリを探していたら、/tmpがあったので、ここにファイルを持ち込んで作業することにしました

2つ目の、別のWindowsのコマンドプロンプトを上げて、今動かしているコンテナを調べました

C:\Users\ebata>docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e87f198ccc07 stefda/osmium-tool "/bin/bash" 12 minutes ago Up 12 minutes wonderful_lumiere

で、ローカルからDockerコンテナの/tmpに直接コピーしました。

C:\Users\ebata>docker cp kanto-latest.osm.pbf wonderful_lumiere:/tmp

一つ目のコマンドプロンプトに戻って、ファイルが可能されているか確認しました。

root@e87f198ccc07:/# cd tmp
root@e87f198ccc07:/tmp# ls
kanto-latest.osm.pbf

ちゃんと入っていました。パスも通っているようですので、

root@e87f198ccc07:/tmp# which osmium
/usr/bin/osmium

そのまま、/tmpの中で作業しました

root@e87f198ccc07:/tmp# osmium tags-filter kanto-latest.osm.pbf w/railway -o kanto-railway-latest.osm.pbf
[======================================================================] 100%
root@e87f198ccc07:/tmp# ls
kanto-latest.osm.pbf kanto-railway-latest.osm.pbf
root@e87f198ccc07:/tmp# osmium export kanto-railway-latest.osm.pbf -o kanto-railway-latest.json
root@e87f198ccc07:/tmp# ls
kanto-latest.osm.pbf kanto-railway-latest.json kanto-railway-latest.osm.pbf

2つ目のコマンドプロンプトを立ち上げて、dockerコンテナから、ローカルに、完成したjsonファイルを送り出します。

C:\Users\ebata\docker-osmium-tool>docker cp wonderful_lumiere:/tmp/kanto-railway-latest.json .

ローカルの方に変換されたファイルが戻ってきました。
C:\Users\ebata>ls
 Dockerfile   README.md   kanto-latest.osm.pbf   kanto-railway-latest.json   work
さて、これを、ドラッグして、QGISに放り込んで出てきた図です。

見事に鉄道だけが抽出されています。

しかし、重要なのはそこではありません。

芳賀・宇都宮LRT が、すでに表示されている ―― これは、今の私にとって、とてつもないインパクトなのです。

さて、今回はLRTを中心とした宇都宮市をカバーする地域の交通機関を切り出します。

【食べログ作れる? 】 OpenStreetMap から Osmiumで飲食店の位置情報を取得してGeoJSONに出力してみたを参照して、

$ osmium extract --bbox 左上の経度,左上の緯度,右下の経度, 右下の緯度 -o 出力したいファイル名.pbf 元のファイル名.osm.pbf

から、

$ osmium extract --bbox 139.76675736787732, 36.659949299138, 140.1596765021647, 36.47004171587971 -o utunomiya-lrt.osm.pbf kanto-latest.osm.pbf

として切り出してみます。

root@80e1a2d0dc02:/tmp# osmium extract --bbox 139.76675736787732, 36.659949299138, 140.1596765021647, 36.47004171587971 -o utunomiya-lrt.osm.pbf kanto-latest.osm.pbf
Error parsing command line: too many positional options have been specified on the command line

あれ、コンマと空白がダメだったかな?

root@80e1a2d0dc02:/tmp# osmium extract --bbox 139.76675736787732,36.659949299138,140.1596765021647,36.47004171587971 -o
utunomiya-lrt.osm.pbf kanto-latest.osm.pbf
Need LEFT < RIGHT and BOTTOM < TOP in --box/-b option.

うむ、 では、36.659949299138 と 36.47004171587971 を入れかえて、再度挑戦

root@80e1a2d0dc02:/tmp# osmium extract --bbox 139.76675736787732,36.47004171587971,140.1596765021647,36.659949299138 -o utunomiya-lrt.osm.pbf kanto-latest.osm.pbf
[======================================================================] 100%

成功したっぽいです。

root@80e1a2d0dc02:/tmp# osmium tags-filter utunomiya-lrt.osm.pbf w/railway -o utunomiya-railway-latest.osm.pbf
[======================================================================] 100%

root@80e1a2d0dc02:/tmp# osmium export utunomiya-railway-latest.osm.pbf -o utunomiya-railway-latest.json

では、ここから、2つ目のコマンドプロンプトから、

C:\Users\ebata\docker-osmium-tool>docker cp serene_shirley:/tmp/utunomiya-railway-latest.json .
C:\Users\ebata\docker-osmium-tool>docker cp serene_shirley:/tmp/utunomiya-lrt.osm.pbf .

としました。

utunomiya-railway-latest.json の表示結果はこちらです。

データ量も、関東全域と比較すると、11%程度になりました。


ところで、osmファイルと、osm.pbfファイルの違いって何だろう?