2023,江端さんの技術メモ

自分用のメモ。PC落すと、忘れてしまうので残します。

目的

バスルートをDBで格納する。

やってきたこと

新規に、データベースを作る。postgisとpgroutingの対応できるようにしておく。

yoko_db=# create database route_db;
CREATE DATABASE
yoko_db=# \c route_db
psql (13.4, server 12.5 (Debian 12.5-1.pgdg100+1))
You are now connected to database "route_db" as user "postgres".
route_db=# CREATE EXTENSION postgis;
CREATE EXTENSION
route_db=# create extension pgrouting;
CREATE EXTENSION

色々分かったこと

geometry型のデータには指定方法がある(らしい)。

http://cse.naro.affrc.go.jp/yellow/pgisman/3.0.0/using_postgis_dbmanagement.html

POINT: SRID指定なしでの2次元ポイントジオグラフィのテーブル生成は次の通りです。デフォルトは4326 WGS84経度緯度となります。

CREATE TABLE ptgeogwgs(gid serial PRIMARY KEY, geog geography(POINT) );
POINT: NAD83経度緯度での2次元ポイントジオグラフィのテーブル生成は次の通りです。

CREATE TABLE ptgeognad83(gid serial PRIMARY KEY, geog geography(POINT,4269) );
Z値を持ち、明示的にSRIDを指定したポイントのテーブル生成は次の通りです。

CREATE TABLE ptzgeogwgs84(gid serial PRIMARY KEY, geog geography(POINTZ,4326) );
LINESTRING

CREATE TABLE lgeog(gid serial PRIMARY KEY, geog geography(LINESTRING) );
POLYGON

-- ポリゴン NAD 1927経度緯度
CREATE TABLE lgeognad27(gid serial PRIMARY KEY, geog geography(POLYGON,4267) );
MULTIPOINT

MULTILINESTRING

MULTIPOLYGON

GEOMETRYCOLLECTION

ジオグラフィ型のフィールドはgeography_columnsシステムビューに登録されます。

"geography_columns"ビューをチェックして、テーブルが一覧にあるか見て下さい。

CREATE TABLEの文法でジオグラフィカラムを持つテーブルを新規に生成できます。

CREATE TABLE global_points (
    id SERIAL PRIMARY KEY,
    name VARCHAR(64),
    location GEOGRAPHY(POINT,4326)
  );

で、ちょっと、試しに、ちょっと以下のgeomデータを入れてみたのですが、興味深い結果が得られました。

route_db=# create table route_geom(geog geography(LINESTRING));
CREATE TABLE
route_db=#
route_db=# insert into route_geom(geog) VALUES('0105000020E610000001000000010200000012000000E97DE36BCF6F6140CDCCCCCCCCCC4140E4141DC9E56F614006D847A7AECC41400D8E9257E76F61400DFD135CACCC4140BEBC00FBE86F6140A27A6B60ABCC4140404D2D5BEB6F61401422E010AACC414045F0BF95EC6F6140A99F3715A9CC4140923F1878EE6F614062F3716DA8CC41400708E6E8F16F61401B47ACC5A7CC4140ABECBB22F86F6140F870C971A7CC414055F65D11FC6F61401B47ACC5A7CC414014ED2AA4FC6F61403F1D8F19A8CC414072A774B0FE6F6140A99F3715A9CC414047ACC5A70070614038F8C264AACC414075C8CD700370614031D3F6AFACCC4140130A117008706140B1E1E995B2CC4140C3B645990D70614006F52D73BACC414043C5387F13706140E23B31EBC5CC4140003ACC9717706140A2D11DC4CECC4140');
ERROR:  Geometry type (MultiLineString) does not match column type (LineString)

とエラーが出てきます。LineStringではなくて、MultiLineStringにしなさい と注意されました。

insertの段階でパースされているとは思いませんでした。

という訳で、次のようにやってみました。

route_db=# drop table route_geom;
DROP TABLE
route_db=# create table route_geom(geog geography(MULTILINESTRING));
CREATE TABLE
route_db=# insert into route_geom(geog) VALUES('0105000020E610000001000000010200000012000000E97DE36BCF6F6140CDCCCCCCCCCC4140E4141DC9E56F614006D847A7AECC41400D8E9257E76F61400DFD135CACCC4140BEBC00FBE86F6140A27A6B60ABCC4140404D2D5BEB6F61401422E010AACC414045F0BF95EC6F6140A99F3715A9CC4140923F1878EE6F614062F3716DA8CC41400708E6E8F16F61401B47ACC5A7CC4140ABECBB22F86F6140F870C971A7CC414055F65D11FC6F61401B47ACC5A7CC414014ED2AA4FC6F61403F1D8F19A8CC414072A774B0FE6F6140A99F3715A9CC414047ACC5A70070614038F8C264AACC414075C8CD700370614031D3F6AFACCC4140130A117008706140B1E1E995B2CC4140C3B645990D70614006F52D73BACC414043C5387F13706140E23B31EBC5CC4140003ACC9717706140A2D11DC4CECC4140');
INSERT 0 1

と、今度はinsertできました。

postGISで接触する線を抽出する方法を探す

のgeomのデータを入力したのですが、座標がおかしい、と、こちらも入力を拒否されました。

2023,江端さんの技術メモ

 

プレゼンテーション1

http://cse.naro.affrc.go.jp/yellow/pgisman/2.2.0/ST_ClosestPoint.html」

http://postgres.cn/docs/postgis-2.3/ST_Buffer.html

このジオメトリ/ジオグラフィからの距離が指定された距離以下となる点全てを表現するジオメトリ/ジオグラフィを返します。

■線の接触を検知するテスト用の線

ちなみに、現実世界の座標では、0,150や100,100などの座標はないのだけど、

create extension postgis
create extension pgrouting

を仕込んだpostGISのDBであれば、こんな訳の分からない座標でもちゃんと計算してくれます。

■線に幅10を与える方法

yoko_db=#
yoko_db=# SELECT ST_Buffer(
yoko_db(# ST_GeomFromText(
yoko_db(# 'LINESTRING(50 50,150 150,150 50)'
yoko_db(# ), 10, 'endcap=round join=round');
st_buffer
-----------------------------------------------------------------------------
01030000000100000031000000AA07FFCFB9DD614056F8003046A26340E82FBFAF370E624050929AFD11CA6340B196BE928A45624085F91736A4E76340E1F76833928162409FA91FEED9F963400000000000C0624000000000000064401F0897CC6DFE62409FA91FEED9F963404F69416D753A634085F91736A4E7634018D04050C871634050929AFD11CA634056F8003046A2634056F8003046A2634050929AFD11CA634018D04050C871634085F91736A4E763404F69416D753A63409FA91FEED9F963401F0897CC6DFE624000000000000064400000000000C06240000000000000644000000000000049409FA91FEED9F9634082DFA3CD4806484085F91736A4E76340C35AFA4A2A16474050929AFD11CA6340A0BFFCBEDE38464056F8003046A26340A71EFC3FE776454018D04050C8716340BEB69509B8D744404F69416D753A6340ED19A0276F6144401F0897CC6DFE624084598147981844400000000000C062400000000000004440E1F76833928162408459814798184440B196BE928A456240EC19A0276F614440E92FBFAF370E6240BEB69509B8D74440AA07FFCFB9DD6140A71EFC3FE7764540B06D6502EEB561409FBFFCBEDE3846407B06E8C95B986140C25AFA4A2A1647406156E0112686614081DFA3CD48064840000000000080614000000000000049400000000000806140A81EFC3FE7765F4058E103C018894C40A81EFC3FE77645405E40034121C74B40BEB69509B8D744403AA505B5D5E94A40EC19A0276F6144407B205C32B7F949408459814798184440FFFFFFFFFFFF4840000000000000444083DFA3CD480648408459814798184440C45AFA4A2A164740ED19A0276F614440A1BFFCBEDE384640BEB69509B8D74440A81EFC3FE7764540A81EFC3FE7764540BFB69509B8D74440A0BFFCBEDE384640EE19A0276F614440C45AFA4A2A164740855981479818444082DFA3CD480648400000000000004440FFFFFFFFFFFF484084598147981844407A205C32B7F94940EC19A0276F6144403AA505B5D5E94A40BDB69509B8D744405D40034121C74B40A81EFC3FE776454058E103C018894C40AA07FFCFB9DD614056F8003046A26340
(1 row)

■幅10の線を交差させる

SELECT ST_AsEWKT(ST_Intersection(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 10, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LINESTRING(0 100,100 100,150 150,0 150)'), 10, 'endcap=round join=round')));

で、その出力値をグラフにするとこうなっていました。

重ね併わせてみます。

線の幅を1にしてみました。

SELECT ST_AsEWKT(ST_Intersection(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LINESTRING(0 100,100 100,150 150,0 150)'), 1, 'endcap=round join=round')));

併わせてみます。

■領域と線の点(ブルーの線)

# SELECT ST_AsEWKT(ST_Intersection(ST_Intersection(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 10, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LINESTRING(0 100,100 100,150 150,0 150)'), 10, 'endcap=round join=round')),ST_GeomFromText('LINESTRING(0 100,100 100,150 150, 0 150)')));

st_asewkt

----------------------------------------------------------------------

LINESTRING(85.857864376269 100,100 100,150 150,135.857864376269 150)

(1 row)

■領域と線の点(オレンジの線)

yoko_db=# SELECT ST_AsEWKT(ST_Intersection(ST_Intersection(ST_Buffer(ST_GeomFromText('LINESTRING(50 50,150 150,150 50)'), 10, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LINESTRING(0 100,100 100,150 150,0 150)'), 10, 'endcap=round join=round')),ST_GeomFromText('LINESTRING(50 50,150 150, 150 50)')));

st_asewkt

------------------------------------------------

LINESTRING(90 90,150 150,150 135.857864376269)

(1 row)

■実世界におけるST_Bufferが作る"幅"は?

先程、『現実世界の座標では、0,150や100,100などの座標はないのだけど、create extension postgis
、create extension pgroutingを仕込んだpostGISのDBであれば、こんな訳の分からない座標でもちゃんと計算してます』と記載しましたが、現実世界の座標では、どうやってこのサイズを決めればいいのかが分かりません。

という訳で実際の座標を使って試してみます。

139.622931 35.489653 と、 139.6401889 35.502831をつかって試してみます。

yoko_db=# SELECT ST_Distance('SRID=4326;POINT(139.622931 35.489653)'::GEOGRAPHY,'SRID=4326;POINT(139.6401889 35.502831)'::GEOGRAPHY);
st_distance
---------------
2142.35290029
(1 row)

ざっくり2km強ですね。

で、ここで、「描画しないと確認が面倒だなぁ」ということに気がつきました。どうやらpgAdminを使えばこれが可能なようです(先程、最新版をインストールして、古いバージョンをアンインストール(デジレクトリレベルで手動で消去しないと、最新版の起動に失敗します)

上記は古いpgadmin4のビューです。新しいpgAdmin4では、これで地図が表示されるようです

 

 

これで、SQL文を入力すると、その絵が出てくる環境が整いました。

で、まずは、

select ST_Buffer(ST_GeomFromText('LINESTRING(139.622931 35.489653, 35.502831 139.6401889)'),10, 'endcap=round join=round')

をやってみました。

定規で図ってみたところ、1/10くらいの幅になっているので、実際この場合、この線の幅は200メートルくらいでしょう。

誤差として50メートルとして、"2"くらいで試してみます。

select ST_Buffer(ST_GeomFromText('LINESTRING(139.622931 35.489653, 35.502831 139.6401889)'),2, 'endcap=round join=round')

よく分からなけど、GPSの誤差を鑑みて、このくらいの幅を取っておくべきかな。とりあえず、これをデフォルトとして、まずはDBを作ってみます。

テストとして、バスルートを記載してみます。

select ST_GeomFromText('LINESTRING(139.595873 35.378154,139.595779 35.37812,139.595766 35.378045,139.595922 35.377752,139.596212 35.377193,139.596704 35.376296,139.596804 35.376139,139.597005 35.375862,139.597443 35.375322,139.597832 35.374847,139.598029 35.374589,139.598145 35.374415,139.598302 35.374138,139.598369 35.374024,139.598422 35.373919,139.598491 35.373738,139.599019 35.372353,139.599495 35.371156,139.599468 35.371081,139.599338 35.371056,139.59899 35.370963,139.597059 35.370473,139.595393 35.370028,139.594419 35.369764,139.59407 35.369659,139.593798 35.369572,139.593476 35.369457,139.592966 35.369259,139.592779 35.369176,139.592465 35.369025,139.591683 35.368627,139.590731 35.368134,139.590073 35.367794,139.589176 35.367331,139.588262 35.366863,139.587735 35.366595,139.587508 35.366492,139.587332 35.366423,139.587176 35.366366,139.586864 35.36627,139.586549 35.36619,139.586207 35.366124,139.585855 35.366079,139.58554 35.366056,139.585204 35.366045,139.584925 35.366056,139.584641 35.366077,139.584445 35.366098,139.584247 35.366129,139.583902 35.366197,139.583561 35.366286,139.583199 35.366401,139.582906 35.366519,139.582565 35.366683,139.582309 35.366816,139.582119 35.366939,139.581933 35.36708,139.581569 35.367314,139.580995 35.367738,139.58035 35.368227,139.57968 35.368715,139.579054 35.36918,139.57882 35.369336,139.578604 35.369493,139.578352 35.369657,139.57811 35.369804,139.577878 35.369934,139.577738 35.37001,139.577589 35.370082,139.577418 35.37016,139.57732 35.37024,139.577346 35.370322,139.577492 35.37064,139.577597 35.370922,139.577664 35.371296,139.577672 35.371576,139.577637 35.371872,139.577594 35.372092,139.577509 35.37234,139.577434 35.372512,139.577012 35.373505,139.576949 35.373583,139.576554 35.374506,139.576609 35.374598,139.576838 35.374662,139.577089 35.374742,139.577114 35.374811,139.577011 35.375071,139.576926 35.375085,139.576557 35.374996,139.576471 35.374952,139.576471 35.374876,139.576599 35.374564,139.576916 35.37381,139.577 35.373789,139.577313 35.373871,139.577775 35.373993,139.57802 35.374046,139.578323 35.374087,139.578673 35.37412,139.578925 35.374131,139.579275 35.374129,139.579628 35.374106,139.580017 35.374068,139.581646 35.373846,139.582008 35.373789,139.582074 35.373858,139.581963 35.374391,139.581837 35.374905,139.581765 35.375135,139.581633 35.375534,139.581499 35.375863,139.58136 35.376167,139.581301 35.376281,139.580964 35.376851,139.580326 35.377869,139.578402 35.380965,139.577251 35.382825,139.577101 35.383021,139.577006 35.383152,139.576828 35.383389,139.576656 35.383641,139.576268 35.38417,139.575843 35.384739,139.575346 35.385415,139.57515 35.385681,139.574998 35.385894,139.574891 35.386055,139.574838 35.386181,139.574775 35.386347,139.574692 35.386563,139.574638 35.386697,139.574542 35.386724,139.574012 35.386604,139.572348 35.386248,139.571268 35.386011,139.570946 35.385959,139.570646 35.385931,139.570348 35.385922,139.570073 35.385934,139.569805 35.385966,139.569487 35.38603,139.568931 35.386182,139.568571 35.386286,139.568349 35.386354,139.568135 35.386442,139.56792 35.386576,139.567769 35.386716,139.56766 35.386853,139.56657 35.388671,139.566268 35.389177,139.566173 35.389362,139.566128 35.389529,139.566101 35.389744,139.566109 35.389929,139.566138 35.390063,139.566177 35.390179,139.566254 35.390338,139.567047 35.391736,139.567153 35.391917,139.567294 35.39212,139.567409 35.392281,139.56767 35.392568,139.568151 35.393053,139.568317 35.393216,139.568846 35.393747,139.569264 35.394174,139.569335 35.394232,139.569419 35.394281,139.569508 35.394292,139.570315 35.394297,139.571173 35.3943,139.571345 35.394307,139.571454 35.394351,139.57165 35.394627,139.572146 35.395394,139.573316 35.397176,139.573368 35.39726,139.573414 35.397345,139.573465 35.397477,139.573485 35.397559,139.573544 35.398079,139.573553 35.398228,139.573516 35.398354,139.573465 35.398447,139.57337 35.398541,139.57296 35.398854,139.57273 35.399024,139.570967 35.400511,139.570926 35.400555,139.570918 35.400594,139.571046 35.400985,139.57111 35.401124,139.571253 35.401304,139.571308 35.401365,139.571382 35.401397,139.571453 35.401399,139.57153 35.401349,139.571716 35.401204,139.571767 35.401149,139.571763 35.401079,139.57172 35.401031,139.571503 35.400935,139.571434 35.40092,139.57138 35.400931,139.57135 35.400995)')

では、次に、このルートに幅を与えてみます。
select ST_Buffer(ST_GeomFromText('LINESTRING(139.595873 35.378154,139.595779 35.37812,139.595766 35.378045,139.595922 35.377752,139.596212 35.377193,139.596704 35.376296,139.596804 35.376139,139.597005 35.375862,139.597443 35.375322,139.597832 35.374847,139.598029 35.374589,139.598145 35.374415,139.598302 35.374138,139.598369 35.374024,139.598422 35.373919,139.598491 35.373738,139.599019 35.372353,139.599495 35.371156,139.599468 35.371081,139.599338 35.371056,139.59899 35.370963,139.597059 35.370473,139.595393 35.370028,139.594419 35.369764,139.59407 35.369659,139.593798 35.369572,139.593476 35.369457,139.592966 35.369259,139.592779 35.369176,139.592465 35.369025,139.591683 35.368627,139.590731 35.368134,139.590073 35.367794,139.589176 35.367331,139.588262 35.366863,139.587735 35.366595,139.587508 35.366492,139.587332 35.366423,139.587176 35.366366,139.586864 35.36627,139.586549 35.36619,139.586207 35.366124,139.585855 35.366079,139.58554 35.366056,139.585204 35.366045,139.584925 35.366056,139.584641 35.366077,139.584445 35.366098,139.584247 35.366129,139.583902 35.366197,139.583561 35.366286,139.583199 35.366401,139.582906 35.366519,139.582565 35.366683,139.582309 35.366816,139.582119 35.366939,139.581933 35.36708,139.581569 35.367314,139.580995 35.367738,139.58035 35.368227,139.57968 35.368715,139.579054 35.36918,139.57882 35.369336,139.578604 35.369493,139.578352 35.369657,139.57811 35.369804,139.577878 35.369934,139.577738 35.37001,139.577589 35.370082,139.577418 35.37016,139.57732 35.37024,139.577346 35.370322,139.577492 35.37064,139.577597 35.370922,139.577664 35.371296,139.577672 35.371576,139.577637 35.371872,139.577594 35.372092,139.577509 35.37234,139.577434 35.372512,139.577012 35.373505,139.576949 35.373583,139.576554 35.374506,139.576609 35.374598,139.576838 35.374662,139.577089 35.374742,139.577114 35.374811,139.577011 35.375071,139.576926 35.375085,139.576557 35.374996,139.576471 35.374952,139.576471 35.374876,139.576599 35.374564,139.576916 35.37381,139.577 35.373789,139.577313 35.373871,139.577775 35.373993,139.57802 35.374046,139.578323 35.374087,139.578673 35.37412,139.578925 35.374131,139.579275 35.374129,139.579628 35.374106,139.580017 35.374068,139.581646 35.373846,139.582008 35.373789,139.582074 35.373858,139.581963 35.374391,139.581837 35.374905,139.581765 35.375135,139.581633 35.375534,139.581499 35.375863,139.58136 35.376167,139.581301 35.376281,139.580964 35.376851,139.580326 35.377869,139.578402 35.380965,139.577251 35.382825,139.577101 35.383021,139.577006 35.383152,139.576828 35.383389,139.576656 35.383641,139.576268 35.38417,139.575843 35.384739,139.575346 35.385415,139.57515 35.385681,139.574998 35.385894,139.574891 35.386055,139.574838 35.386181,139.574775 35.386347,139.574692 35.386563,139.574638 35.386697,139.574542 35.386724,139.574012 35.386604,139.572348 35.386248,139.571268 35.386011,139.570946 35.385959,139.570646 35.385931,139.570348 35.385922,139.570073 35.385934,139.569805 35.385966,139.569487 35.38603,139.568931 35.386182,139.568571 35.386286,139.568349 35.386354,139.568135 35.386442,139.56792 35.386576,139.567769 35.386716,139.56766 35.386853,139.56657 35.388671,139.566268 35.389177,139.566173 35.389362,139.566128 35.389529,139.566101 35.389744,139.566109 35.389929,139.566138 35.390063,139.566177 35.390179,139.566254 35.390338,139.567047 35.391736,139.567153 35.391917,139.567294 35.39212,139.567409 35.392281,139.56767 35.392568,139.568151 35.393053,139.568317 35.393216,139.568846 35.393747,139.569264 35.394174,139.569335 35.394232,139.569419 35.394281,139.569508 35.394292,139.570315 35.394297,139.571173 35.3943,139.571345 35.394307,139.571454 35.394351,139.57165 35.394627,139.572146 35.395394,139.573316 35.397176,139.573368 35.39726,139.573414 35.397345,139.573465 35.397477,139.573485 35.397559,139.573544 35.398079,139.573553 35.398228,139.573516 35.398354,139.573465 35.398447,139.57337 35.398541,139.57296 35.398854,139.57273 35.399024,139.570967 35.400511,139.570926 35.400555,139.570918 35.400594,139.571046 35.400985,139.57111 35.401124,139.571253 35.401304,139.571308 35.401365,139.571382 35.401397,139.571453 35.401399,139.57153 35.401349,139.571716 35.401204,139.571767 35.401149,139.571763 35.401079,139.57172 35.401031,139.571503 35.400935,139.571434 35.40092,139.57138 35.400931,139.57135 35.400995)'),1, 'endcap=round join=round')

あれ? この円は何? と思い色々弄っていたのですが

を、変えてみました。

としてみました。

"当たり"だったようです。

ただ、これでは幅をメートルで設定できないので、メートル設定をしてみました.

GPSの誤差として50メートルくらいでいいかな、と

SELECT ST_Buffer(st_transform(st_setsrid(ST_GeomFromtext(
'linestring(139.595873 35.378154,139.595779 35.37812,139.595766 35.378045,..... ,139.57138 35.400931,139.57135 35.400995)'
),4326),3857),50,'endcap=round join=round')

 

2023,江端さんの技術メモ

osm.pbfファイルを、osmファイルに変換する方法

■pbfからpbfを切り出す

osmconvert chugoku-latest.osm.pbf -b=133.455201,34.274923,134.298740,35.284564 --complete-ways -o=okayama.pbf

は、chugoku-latest.osm.pbf から、133.455201,34.274923,134.298740,35.284564の領域で、Pbfファイルとして切り取れ。

■osmをpbfに変換する

osmconvert yokohama.osm --out-pbf >yokohama.pbf

 

2023,江端さんの技術メモ

OpenStreetMapとOpenTripPlannerで経路検索してバスに乗れと言ってもらった

オープンソースの経路探索「OpenTripPlanner」をUbuntuで動かして岡山県で経路探索をする

上記の「岡山県」を丸ごと試させて頂いています。

違いがあるのは、私は、>wsl -d Ubuntu-20.04  on Windows10を使っているところと、

「Windows11にWSL2+Ubuntu20.04をインストールする」を試してみて、Golangをインストールしてみた件

otp-1.3.0-shaded.jar → 1.4.0のバージョンアップ版を使っているところです。

中々 Grizzly server running.  の表示が出てこないので、1.4.0を使ったということと、あと、

wget "http://www.shimoden.net/busmada/opendata/GTFS-JP.zip" -O shimodenbus.gtfs.zip

だけにしたところです。全部入れたら、エラーになるので、どれかのファイルに問題があるものと思います。

後は、明日以降にします。


横浜市交通局 バスの情報に適用します

https://ckan.odpt.org/dataset?q=%E6%A8%AA%E6%B5%9C&tags=%E3%83%90%E3%82%B9-bus&sort=score+desc%2C+metadata_modified+desc

から、データをダウンロードします。

これを、C:\Users\ebata\yokohama\yoko_db などを作っておき、

>wsl -d Ubuntu-20.04

を立ち上げて、osmをpbfに変換する

osmconvert yokohama.osm --out-pbf >yokohama.pbf

を作ります。

osmconvertの使い方

 

C:\Users\ebata\yokohama\yoko_dbに移動して、

ebata@DESKTOP-P6KREM0:/mnt/c/Users/ebata/yokohama/yoko_db$ java -Xmx5G -jar otp-1.4.0-shaded.jar --build ./ --inMemory

を実施すると、サーバが立ち上がります。

ebata@DESKTOP-P6KREM0:/mnt/c/Users/ebata/yokohama/otp$ java -Xmx5G -jar otp-1.4.0-shaded.jar --build ./ --inMemory
14:24:02.863 INFO (OTPServer.java:39) Wiring up and configuring server.
14:24:02.868 INFO (GraphBuilder.java:165) Wiring up and configuring graph builder task.
14:24:02.870 INFO (GraphBuilder.java:171) Searching for graph builder input files in .
(中略)
14:24:13.837 INFO (NetworkListener.java:750) Started listener bound to [0.0.0.0:8080]
14:24:13.849 INFO (NetworkListener.java:750) Started listener bound to [0.0.0.0:8081]
14:24:13.851 INFO (HttpServer.java:300) [HttpServer] Started.
14:24:13.851 INFO (GrizzlyServer.java:153) Grizzly server running.

http://localhost:8080/

をすると、以下のような画面が出てきます。

この使い方は後回しにして、とりあえず以下を確認します。

http://localhost:8080/otp/routers/default/index/agencies/1

http://localhost:8080/otp/routers/default/index/agencies/1/3000020141003/routes

使い方は、まだ良く分かりませんし、エラーも出ていますが、今日はここまで。

 

2023,江端さんの技術メモ

いつも通り、2台目のパソコンにdockerを使ったDBを作成して、psqlを使って、DBアクセスを試みましたが、

postgres=# \c yoko_db
psql (13.4, server 12.5 (Debian 12.5-1.pgdg100+1))
You are now connected to database "yoko_db" as user "postgres".
yoko_db=# \dt
List of relations
Schema | Name | Type | Owner
--------+-------------------+-------+----------
public | configuration | table | postgres
public | pointsofinterest | table | postgres
public | spatial_ref_sys | table | postgres
public | ways | table | postgres
public | ways_vertices_pgr | table | postgres
(5 rows)

yoko_db=# select * from ways;
ERROR: character with byte sequence 0xe9 0xb7 0x97 in encoding "UTF8" has no equivalent in encoding "SJIS"

という見たことのないエラーが出てきました。

ローカルからアクセスしたところ問題はないようですし、As:SQL Mk-2を使ってリモートからも読めました。

A5:SQL Mk-2 トライアル(解決)

psqlでログインして、\c (テーブル) (ここの場合は、# \c yoko_db)とした後、

yoko_db=# set client_encoding to utf8;
SET
yoko_db=# \encoding
UTF8

とすることで、エラーが出てこなくなりました。

以上

 

2023,江端さんの技術メモ

type testJSON struct {
	Id      string `json:"@id"`
	Type    string `json:"@type"`
	Date    string `json:"dc:date"`
	Context string `json:"@context"`
	Title   string `json:"dc:title"`
	Note    string `json:"odpt:note"`
	Regions struct {
		Type        string `json:"type"`
		Coordinates []interface{}
	} `json:"ug:region"`
	Owl              string `json:"owl:sameAS"`
	Pattern          string `json:"odpt:pattern"`
	Busroute         string `json:"odpt:busroute"`
	Operator         string `json:"odpt:operator"`
	Direction        string `json:"odpt:direction"`
	BusstopPoleOrder []struct {
		Note        string `json:"odpt:note"`
		Index       int    `json:"odpt:index"`
		BusstopPole string `json:"odpt:busstopPole"`
	} `json:"odpt:busstopPoleOrder"`
}

の、

Coordinates []interface{}

の部分をsrting型→分割 → float64型に戻して処理しようしているのですが、

for _, e := range data {
		fmt.Println(e) // 全情報表情
		coors := e.Regions.Coordinates
		fmt.Println("len_mm", len(coors))
		for _, coor := range coors {
			fmt.Println(coor)
			str_coor := coor.(string)
			arr := strings.Split(str_coor, " ")
			lng := arr[0]
			lat := arr[1]
			fmt.Println(lng, lat)
		}
	}

[139.6249873 35.4648941]
panic: interface conversion: interface {} is []interface {}, not string

goroutine 1 [running]:
main.main()
C:/Users/ebata/yoko_bus_route/route_json.go:92 +0x694
exit status 2

というエラーが出てきて停止します ―― キャストできない。

多分対応方法はあると思うのですが、調べるのが面倒なので、C/C++で定番の、あの美しくない方法 "sprintf"を使いました

for _, coor := range coors {

			//	str_coor := coor.(string)   
			str_coor := fmt.Sprintf("%v", coor) // この段階では" [139.575019, 35.439622]"という文字列 

			arr := strings.Split(str_coor, " ") // スペース" " で arr[0], arr[1]という文字列に分離する

			str_lng := strings.Replace(arr[0], "[", "", -1) // arr[0] ("[139.575019")から、"["を消す
			str_lat := strings.Replace(arr[1], "]", "", -1) // arr[1] ("35.439622]")から、"]"を消す

			lng64, _ := strconv.ParseFloat(str_lng, 64) // float64に強制的に型変換する
			lat64, _ := strconv.ParseFloat(str_lat, 64) // float64に強制的に型変換する

			fmt.Println("lng:", lng64)
			fmt.Println("lat:", lat64)

		}

動けばいいんですよ、動けば。

2023,江端さんの技術メモ

https://ckan.odpt.org/dataset/b_busstop-yokohamamunicipal/resource/da256719-0c39-48a7-a1f2-20354e18d529

// stop_json.go
// 横浜市交通局 バス停情報 / Bus stop information of Transportation Bureau, City of Yokohama
// https://ckan.odpt.org/dataset/b_busstop-yokohamamunicipal

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

type stopJSON struct {
	Id        string `json:"@id"`
	Type      string `json:"@type"`
	Lng_Title struct {
		En      string `json:"en"`
		Ja      string `json:"ja"`
		Ko      string `json:"ko"`
		Zh      string `json:"zh"`
		Ja_Hrkt string `json:"ja-Hrkt"`
	} `json:"title"`
	Date              string        `json:"dc:date"`
	Lat               float64       `json:"geo:lat"`
	Long              float64       `json:"geo:long"`
	Context           string        `json:"@context"`
	Title             string        `json:"dc:title"`
	Kana              string        `json:"odpt:kana"`
	SameAS            string        `json:"owl:sameAS"`
	Operator          []interface{} `json:"odpt:operator"`
	BusroutePattern   []interface{} `json:"odpt:busroutePattern"`
	BusstopPoleNumber string        `json:"odpt:busstopPoleNumber"`
	// BusstopPoleTimetable
}

func main() {

	// JSONファイルから読み取る場合
	//file, err := ioutil.ReadFile("route.json")
	//file, err := ioutil.ReadFile("odpt_BusroutePattern.json")
	file, err := ioutil.ReadFile("odpt_BusstopPole.json")
	if err != nil {
		// エラー処理
	}

	/*
		// Webアクセスで取得する場合

		client := &http.Client{}
		req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusstopPole?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
		if err != nil {
			log.Fatal(err)
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()
		if err != nil {
			log.Fatal(err)
		}

		file, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
	*/

	var data []stopJSON

	err = json.Unmarshal(file, &data)
	if err != nil {
		fmt.Println("Unmarshal")
		log.Fatal(err)
	}

	//fmt.Println(string(file))

	// ループによる取得
	for _, e := range data {

		fmt.Println(e) // 全情報表情

		stops := e.BusroutePattern
		fmt.Println("len_stop", len(stops))

		for _, stop := range stops {
			fmt.Println(stop)
		}

	}
}

2023,江端さんの技術メモ

https://ckan.odpt.org/dataset/b_busroute-yokohamamunicipal


■動かなくて悩んでいたコード

// xml_parse.go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type testXML struct {
	Date     string `json:"dc:date"`
	Busroute string `json:"odpt:BusroutePattern"`
}

func main() {
	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
	if err != nil {
		log.Fatal(err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	//fmt.Println(body)

	var data testXML

	err = json.Unmarshal(body, &data)
	if err != nil {
		fmt.Println("Unmarshal")
		log.Fatal(err)

	}

	fmt.Println(data)

}

出力結果

> go run xml_parse.go
Unmarshal
2023/05/01 18:05:31 json: cannot unmarshal array into Go value of type main.testXML
exit status 1

悩むこと1時間、("type testXML"スキーマを色々いじったりしていた)

『そういえば、このjsonのデータは、違う路線データも入っているはずだよな』
→ 『ということは、複数のデータが入っていることになるよな』
→ 『 For文で取らなければ不味くないか?』

で、ググってみたら、どうやらループになるように仕込んでおかないといけないらしいことが分かりました(XMLの先頭の情報が取れるだけだと思っていた)

■動いたコード

// xml_parse.go
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type testXML struct {
	Date     string `json:"dc:date"`
	Busroute string `json:"odpt:BusroutePattern"`
}

func main() {
	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
	if err != nil {
		log.Fatal(err)
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	if err != nil {
		log.Fatal(err)
		x
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	//fmt.Println(body)

	var data []testXML

	err = json.Unmarshal(body, &data)
	if err != nil {
		fmt.Println("Unmarshal")
		log.Fatal(err)

	}

	for _, e := range data {
		fmt.Println(e.Date)
	}

}

go run xml_parse.go
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00
2023-04-03T00:00:00+09:00"当たり"だったようです。


バス路線情報のJSONをブラウザで読みとってみました。


ここから、酷いハマり方をします(10時間くらい)。

上記のルートの位置情報の表示のコーディングがどうしても分からない。

以下は、上記のJSONを簡略化したものです(route.json)。

[
	{
		"dc:date":"2023-04-03T00:00:00+09:00",
		"dc:title":"007",
		"ug:region":
				{
					"type":"Triangle",
					"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]
				},
		"owl:sameAs":"test1"
	},
	{
		"dc:date":"2023-04-03T00:00:00+09:00",
		"dc:title":"008",
		"ug:region":
				{
					"type":"LineString",
					"coordinates":[[139.667765,35.416456],[139.668006,35.416708],[139.668116,35.416788],[139.668276,35.416841]]
				},
		"owl:sameAs":"test2"
	}
]

問題は、ネストの中に入っていて、タグの名前のついていない、

"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]

の部分です。

10時間くらいは戦ったかなぁ ―― もう疲れ果てて、質問サイトに投稿する為に、(上記の)JSONファイルと、このパーサープログラムを成形していました。

で、そのプログラムをテストランさせたら ―― これが動いてしまったのです。

// route_json.go

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

type testXML struct {
	Date    string `json:"dc:date"`
	Title   string `json:"dc:title"`
	Note    string `json:"odpt:note"`
	Owl     string `json:"owl:sameAS"`
	Regions struct {
		Type        string `json:"type"`
		Coordinates []interface{}
		//Coordinates []struct {
		//	Longitude float64 `json:"longitudes>longitude"`
		//	Latitude  float64 `json:"latitudes>latitude "`
		//} `json:"coodtinates"`
	} `json:"ug:region"`
}

func main() {
	file, err := ioutil.ReadFile("route.json")
	//file, err := ioutil.ReadFile("odpt_BusroutePattern.json")

	if err != nil {
		// エラー処理
	}

	var data []testXML

	err = json.Unmarshal(file, &data)
	if err != nil {
		fmt.Println("Unmarshal")
		log.Fatal(err)
	}

	fmt.Println(string(file))

	// ループによる取得
	for _, e := range data {

		fmt.Println(e) //
		mm := e.Regions.Coordinates

		fmt.Println(len(mm))

		for _, m := range mm {
			fmt.Println(m)
		}
	}
}

実行結果は以下の通りです。

> go run route_json.go
[
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"007",
"ug:region":
{
"type":"Triangle",
"coordinates":[[139.6249873,35.4648941],[139.6237514,35.4648862],[139.623672,35.4650137]]
},
"owl:sameAs":"test1"
},
{
"dc:date":"2023-04-03T00:00:00+09:00",
"dc:title":"008",
"ug:region":
{
"type":"LineString",
"coordinates":[[139.667765,35.416456],[139.668006,35.416708],[139.668116,35.416788],[139.668276,35.416841]]
},
"owl:sameAs":"test2"
}
]

{2023-04-03T00:00:00+09:00 007 test1 {Triangle [[139.6249873 35.4648941] [139.6237514 35.4648862] [139.623672 35.4650137]]}}
3
[139.6249873 35.4648941]
[139.6237514 35.4648862]
[139.623672 35.4650137]
{2023-04-03T00:00:00+09:00 008 test2 {LineString [[139.667765 35.416456] [139.668006 35.416708] [139.668116 35.416788] [139.668276 35.416841]]}}
4
[139.667765 35.416456]
[139.668006 35.416708]
[139.668116 35.416788]
[139.668276 35.416841]

ずっとエラーが出ていたんですが、突然表示されるようになりました。

        Coordinates []interface{}

JSONタグが付いていない問題は、これで解消できるようです。

で、(簡易版ではなく)実体のJSONファイルを使ってみても動きました。

こういうことがあるから、本当にコーディングというのは厄介なんですよね。


パースしたデータを表示してみました。

通路が(多分)順番に表示されることが確認できました。


だいたいこれで完成です。

// route_json.go
// 横浜市交通局 バス路線情報 / Bus route information of Transportation Bureau, City of Yokohama
// https://ckan.odpt.org/dataset/b_busroute-yokohamamunicipal

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
)

type testJSON struct {
	Id      string `json:"@id"`
	Type    string `json:"@type"`
	Date    string `json:"dc:date"`
	Context string `json:"@context"`
	Title   string `json:"dc:title"`
	Note    string `json:"odpt:note"`
	Regions struct {
		Type        string `json:"type"`
		Coordinates []interface{}
	} `json:"ug:region"`
	Owl              string `json:"owl:sameAS"`
	Pattern          string `json:"odpt:pattern"`
	Busroute         string `json:"odpt:busroute"`
	Operator         string `json:"odpt:operator"`
	Direction        string `json:"odpt:direction"`
	BusstopPoleOrder []struct {
		Note        string `json:"odpt:note"`
		Index       int    `json:"odpt:index"`
		BusstopPole string `json:"odpt:busstopPole"`
	} `json:"odpt:busstopPoleOrder"`
}

func main() {

	// JSONファイルから読み取る場合
	//file, err := ioutil.ReadFile("route.json")
	file, err := ioutil.ReadFile("odpt_BusroutePattern.json")
	if err != nil {
		// エラー処理
	}

	/*
		// Webアクセスで取得する場合

		client := &http.Client{}
		req, err := http.NewRequest("GET", "https://api.odpt.org/api/v4/odpt:BusroutePattern?odpt:operator=odpt.Operator:YokohamaMunicipal&acl:consumerKey=f4954c3814b207512d8fe4bf10f79f0dc44050f1654f5781dc94c4991a574bf4", nil)
		if err != nil {
			log.Fatal(err)
		}

		resp, err := client.Do(req)
		if err != nil {
			log.Fatal(err)
		}
		defer resp.Body.Close()
		if err != nil {
			log.Fatal(err)
		}

		file, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			log.Fatal(err)
		}
	*/

	var data []testJSON

	err = json.Unmarshal(file, &data)
	if err != nil {
		fmt.Println("Unmarshal")
		log.Fatal(err)
	}

	//fmt.Println(string(file))

	// ループによる取得
	for _, e := range data {

		fmt.Println(e) // 全情報表情
		coors := e.Regions.Coordinates

		fmt.Println("len_mm", len(coors))

		for _, coor := range coors {
			fmt.Println(coor)
		}

		stops := e.BusstopPoleOrder

		fmt.Println("len_ss", len(stops))

		for _, stop := range stops {
			fmt.Println(stop.Note)
			fmt.Println(stop.Index)
			fmt.Println(stop.BusstopPole)
		}

	}
}

2023,江端さんの技術メモ

1 目的

すぐに環境やらコード(の場所)やらを忘れるので、自分の為にメモを残す

2 簡単なfastapiの環境とコード

私の環境では、c:\users\ebata\fastapi1,2,3,4,5あたりにある。

2.1. 最新のPIPをアップグレードする

> pip install --upgrade pip
> python -m pip install --upgrade pip
> python3 -m pip install --upgrade pip

のいずれかでできる。

2.2. uvicorn, fastapi, pydantic, pandas, requests, jsonのモジュールをインストールする

> pip3 install fastapi
> pip3 install uvicorn[standard]

は必須だが、後はプログラムを動かせば、プログラムの方から、インストールを指示されると思うので大丈夫(だろう)。

2.3. uvicornを起動する

> uvicorn index:app --reload

これで、自動的に"index.py"が動き出す。

2.4. クライアントを起動する

> python request.py

で、起動を確認する。

以上

3. ソースコード

3.1. index.py

# https://miseruit.com/2022/07/18/post-2939/

from fastapi import FastAPI
from pydantic import BaseModel
import pandas

class Item(BaseModel):
    ID: str
    Name: str
    Class: str

app = FastAPI() #インスタンスを作成

User_list =[
    {"ID":"M001","Name":"Takashi","Class":"A"},
    {"ID":"M002","Name":"Hanako","Class":"A"},
    {"ID":"M003","Name":"Hiroshi","Class":"B"},
    {"ID":"M004","Name":"Kyoko","Class":"B"},
    ]

# joson_normalize関数を用いてJsonデータをDataFrame型に変換します。
df = pandas.json_normalize(User_list)

# Get /Users/ : 全件取得
# Get /Users/{ID} :特定のIDのみ取得
# POST /Users/ :ユーザの登録
# DELETE /Users/{ID} :ユーザの削除

# curl http://localhost:8000/Users/
@app.get("/Users/")
async def users():
    return User_list

# curl http://localhost:8000/Users/M003
@app.get("/Users/{u_id}")
async def users(u_id:str):
    return list(filter(lambda item : item['ID']==u_id, User_list))

# Windowsの場合 
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/

@app.post("/Users/")
async def users(user: Item):
    User_list.append({"ID": user.ID,"Name":user.Name,"Class":user.Class})
    return User_list

# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003
@app.delete("/Users/{u_id}")
async def users(u_id:str):
    global df
    df = df.drop(df.index[df["ID"]==u_id])
    return df.to_json(orient='records')

3.2. request.py

import requests
import json

# テスト
#url = 'https://wp.kobore.net/'
#response = requests.get(url)
#print(response.text)

#print()

print("-----start of get(1)")

# curl http://localhost:8000/Users/ と同じ機能を実施
r = requests.get('http://localhost:8000/Users/')

print(r.url)
print(r.status_code)
print(r.text)
print(r.json())

print("-----end of get(1)")

print()

# curl http://localhost:8000/Users/M003 と同じ機能を実施

print("-----start of get(2)")

r = requests.get('http://localhost:8000/Users/M003')

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(2)")

print()
print("-----start of delete")
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003と同じ機能を実施
r = requests.delete('http://localhost:8000/Users/M003')

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())

print("-----end of delete")
print()

# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/ と同じ機能を実現
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する

headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
json_payload = '{"ID":"M005","Name":"Aya","Class":"C"}'  # ここで100回くらいのパターンを試したぞ

print("headers:",headers)
print("payload:",json_payload)

#r = requests.post('http://localhost:8000/Users/',headers=headers, params=payload)
#r = requests.post('http://localhost:8000/Users/', headers=headers,params=json.loads(payload))
r = requests.post('http://localhost:8000/Users/', headers=headers, data=json_payload)

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())

2023,江端さんの技術メモ

結論から先に言うと

json_payload ='{"ID":"M005","Name":"Aya","Class":"C"}'
'"が付いているかどうか、だったというオチでした。

pythonでFastAPIを試す

のように、pythonでFastAPIを使って、外部と内部のインターフェースのテンプレートを作るというのが、目下の課題でした。
多くのサンプルプログラムがあったのですが、私はhttpのGET/POSTができれば十分だったので、他のことは忘れて、そのポイントのみに絞って、ネットを探し回りました。
まずは、FastAPIを受ける側(サーバと言えるのかな)のプログラムは、

Python | FastAPIでAPI作成 ~その6:DELETEでデータ削除 & Pandas活用

を参考(というかコピペ)させて頂きました。

以下のコードを、私の場合はC:\Users\ebata\fastapi4に、index.pyという名前で置きました。
# https://miseruit.com/2022/07/18/post-2939/

from fastapi import FastAPI
from pydantic import BaseModel
import pandas

class Item(BaseModel):
    ID: str
    Name: str
    Class: str

app = FastAPI() #インスタンスを作成

User_list =[
    {"ID":"M001","Name":"Takashi","Class":"A"},
    {"ID":"M002","Name":"Hanako","Class":"A"},
    {"ID":"M003","Name":"Hiroshi","Class":"B"},
    {"ID":"M004","Name":"Kyoko","Class":"B"},
    ]

# joson_normalize関数を用いてJsonデータをDataFrame型に変換します。
df = pandas.json_normalize(User_list)

# Get /Users/ : 全件取得
# Get /Users/{ID} :特定のIDのみ取得
# POST /Users/ :ユーザの登録
# DELETE /Users/{ID} :ユーザの削除

# curl http://localhost:8000/Users/
@app.get("/Users/")
async def users():
    return User_list


# curl http://localhost:8000/Users/M003
@app.get("/Users/{u_id}")
async def users(u_id:str):
    return list(filter(lambda item : item['ID']==u_id, User_list))


# Windowsの場合 
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する
# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/

@app.post("/Users/")
async def users(user: Item):
    User_list.append({"ID": user.ID,"Name":user.Name,"Class":user.Class})
    return User_list

# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003
@app.delete("/Users/{u_id}")
async def users(u_id:str):
    global df
    df = df.drop(df.index[df["ID"]==u_id])
    return df.to_json(orient='records')
このプログラムは、メモリ上の名簿を検索(GET)、追加(POST)、削除(DELETE)するものです。

C:\Users\ebata\fastapi4> uvicorn index:app --reload

で起動します。
多分、プログラムの方から、「pipでxxxxを入れろ」と文句を言ってきますので、大人しく従います。
プログラム中に記載されている、curlのコマンドを使うとFastAPIの動作を確認できます。
さて、このプログラムで基本動作は確認できますが、基本的にクライアントのプログラムの叩き台がなければ、使えません。モジュールプログラムの中から、curlのコマンドを発行しろというのも乱暴な話です。
ですので、サクッとクライアント(というかFastAPIのアクセス用のテストプログラム)を作成してしまうと思ったのですが、ここで嵌りました。
先ずは、以下のコードをrequest.pyという名前で、C:\Users\ebata\fastapi4に置きました。
(以下のファイルは、動きたてのコードを修正せずに、汚いままで展開しています)
import requests
import json

# テスト
#url = 'https://wp.kobore.net/'
#response = requests.get(url)
#print(response.text)

#print()

print("-----start of get(1)")

# curl http://localhost:8000/Users/ と同じ機能を実施
r = requests.get('http://localhost:8000/Users/')

print(r.url)
print(r.status_code)
print(r.text)
print(r.json())


print("-----end of get(1)")

print()

# curl http://localhost:8000/Users/M003 と同じ機能を実施

print("-----start of get(2)")

r = requests.get('http://localhost:8000/Users/M003')

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())
print("-----end of get(2)")


print()
print("-----start of delete")
# curl -X DELETE -H "accept: application/json" http://localhost:8000/Users/M003と同じ機能を実施
r = requests.delete('http://localhost:8000/Users/M003')

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())

print("-----end of delete")
print()



# curl -X POST -H "accept: application/json" -H "Content-Type: application/json" -d "{\"ID\":\"M005\", \"Name\":\"Aya\", \"Class\":\"C\"}" http://localhost:8000/Users/ と同じ機能を実現
# {"ID":"M005","Name":"Aya","Class":"C"}]を追加する

headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
json_payload = '{"ID":"M005","Name":"Aya","Class":"C"}'  # ここで100回くらいのパターンを試したぞ

print("headers:",headers)
print("payload:",json_payload)

#r = requests.post('http://localhost:8000/Users/',headers=headers, params=payload)
#r = requests.post('http://localhost:8000/Users/', headers=headers,params=json.loads(payload))
r = requests.post('http://localhost:8000/Users/', headers=headers, data=json_payload)

print(r.url)

print(r.status_code)
print(r.text)
print(r.json())
で、
C:\Users\ebata\fastapi4> python request.py
にて、GETとDELETEは問題なくサクっと動いたのですが、POSTだけが思うように動かない
{'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]}
{"detail":[{"loc":["body"],"msg":"field required","type":"value_error.missing"}]}
エラー番号422
やらが、ワラワラと出てきて、頭を抱えました。
色々検索して調べると『こうやればいい』というアドバイスだけして、サンプルコード(1行で足る)を残さない奴が、世界中には山ほどいます(アドバイスではなく、コードを残して欲しい)。
のコードを真似て、ようやく動かすことができました(本当に助かりました)。
とりあえず、これでサーバとクライアントの対(つい)が完成し、実プログラムに展開できる準備ができたと思います。

以上