行政データのプロット

行政データのプロット

今回は国土数値情報の行政区域データを使う

https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v2_3.html
これは市区町村の境界が表示できる

今回は東京都の平成29年のものを使う
N03-170101_13_GML.zip
をダウンロード

ダブルクリックで解凍し

mv ~/Downloads/N03-170101_13_GML .

で移動

この中にあるシェープファイルを使う
N03-17_13_170101.shp
を使う

拡張子 .shpがシェープファイル

まずはこのファイルの読み込み

gyosei = gpd.read_file('./N03-170101_13_GML/N03-17_13_170101.shp')
gyosei

N03_001	N03_002	N03_003	N03_004	N03_007	geometry	
0	東京都	None	千代田区	None	13101	POLYGON ((139.77287 35.7037, 139.77279 35.7031...
1	東京都	None	中央区	None	13102	POLYGON ((139.78341 35.69645, 139.78459 35.696...
2	東京都	None	港区	None	13103	POLYGON ((139.77129 35.62841, 139.77128 35.628...
3	東京都	None	港区	None	13103	POLYGON ((139.76689 35.62774, 139.76718 35.627...
4	東京都	None	港区	None	13103	POLYGON ((139.77022 35.63199, 139.77046 35.631...
...	...	...	...	...	...	...
6205	東京都	None	None	所属未定地	None	POLYGON ((139.8413 35.64702, 139.84131 35.6471...
6206	東京都	None	None	所属未定地	None	POLYGON ((139.80438 35.60061, 139.80399 35.600...
6207	東京都	None	None	所属未定地	None	POLYGON ((139.81937 35.60899, 139.81923 35.608...
6208	東京都	None	None	所属未定地	None	POLYGON ((139.81009 35.61355, 139.81069 35.613...
6209	東京都	None	None	所属未定地	None	POLYGON ((139.82664 35.5976, 139.827 35.59703,...
6210 rows × 6 columns

となる

次にこの中から品川区のデータに絞るため
まずは区のみに絞る

そのためにまずはNoneのあるデータを除く

gyosei_tmp = gyosei.dropna(subset=['N03_003'])
gyosei_tmp

これで

N03_001	N03_002	N03_003	N03_004	N03_007	geometry	
0	東京都	None	千代田区	None	13101	POLYGON ((139.77287 35.7037, 139.77279 35.7031...
1	東京都	None	中央区	None	13102	POLYGON ((139.78341 35.69645, 139.78459 35.696...
2	東京都	None	港区	None	13103	POLYGON ((139.77129 35.62841, 139.77128 35.628...
3	東京都	None	港区	None	13103	POLYGON ((139.76689 35.62774, 139.76718 35.627...
4	東京都	None	港区	None	13103	POLYGON ((139.77022 35.63199, 139.77046 35.631...
...	...	...	...	...	...	...
140	東京都	None	西多摩郡	瑞穂町	13303	POLYGON ((139.35786 35.74464, 139.35776 35.744...
141	東京都	None	西多摩郡	瑞穂町	13303	POLYGON ((139.35 35.79414, 139.35016 35.79364,...
142	東京都	None	西多摩郡	日の出町	13305	POLYGON ((139.19302 35.78875, 139.19307 35.788...
143	東京都	None	西多摩郡	檜原村	13307	POLYGON ((139.10611 35.77867, 139.10644 35.778...
144	東京都	None	西多摩郡	奥多摩町	13308	POLYGON ((139.01912 35.89826, 139.01941 35.898...

となる

次にこのデータから区を含むものだけに絞り込む

gyosei_ku = gyosei_tmp['N03_003'].str.contains('区')
gyosei_ku

とすれば

0       True
1       True
2       True
3       True
4       True
       ...  
140    False
141    False
142    False
143    False
144    False
Name: N03_003, Length: 117, dtype: bool

となって判別できるので

この条件式を使って

gyosei_ku = gyosei_tmp[gyosei_tmp['N03_003'].str.contains('区')]
gyosei_ku

とすれば

N03_001	N03_002	N03_003	N03_004	N03_007	geometry	
0	東京都	None	千代田区	None	13101	POLYGON ((139.77287 35.7037, 139.77279 35.7031...
1	東京都	None	中央区	None	13102	POLYGON ((139.78341 35.69645, 139.78459 35.696...
2	東京都	None	港区	None	13103	POLYGON ((139.77129 35.62841, 139.77128 35.628...
3	東京都	None	港区	None	13103	POLYGON ((139.76689 35.62774, 139.76718 35.627...
4	東京都	None	港区	None	13103	POLYGON ((139.77022 35.63199, 139.77046 35.631...
...	...	...	...	...	...	...
107	東京都	None	葛飾区	None	13122	POLYGON ((139.87626 35.79479, 139.87661 35.793...
108	東京都	None	江戸川区	None	13123	POLYGON ((139.86285 35.63532, 139.86299 35.635...
109	東京都	None	江戸川区	None	13123	POLYGON ((139.8638 35.63722, 139.86391 35.6371...
110	東京都	None	江戸川区	None	13123	POLYGON ((139.8556 35.63856, 139.85563 35.6385...
111	東京都	None	江戸川区	None	13123	POLYGON ((139.89018 35.75055, 139.89044 35.750...
112 rows × 6 columns

というように区のみにできる

これで東京23区の表示をする

gyosei_ku.plot()

これで区の区切りが白い線になっているのがわかる

次に品川区のみ色を変更する

ax = gyosei_ku.plot()
gdf.plot(ax=ax,color='orange',markersize=1)
ax = gyosei_ku.plot()
gdf.plot(ax=ax,color='orange',markersize=1)

次に品川のみに絞り込む

shinagawa = gyosei[gyosei['N03_003']=='品川区']
shinagawa

これで

N03_001	N03_002	N03_003	N03_004	N03_007	geometry	
87	東京都	None	品川区	None	13109	POLYGON ((139.75501 35.61771, 139.75507 35.617...
88	東京都	None	品川区	None	13109	POLYGON ((139.77231 35.62188, 139.77302 35.621...
89	東京都	None	品川区	None	13109	POLYGON ((139.75953 35.625, 139.75966 35.6216,...
90	東京都	None	品川区	None	13109	POLYGON ((139.71943 35.64167, 139.71953 35.641...

となる

次に品川区のみ表示する

ax = shinagawa.plot()
gdf.plot(ax=ax,color='orange',markersize=2)


CSVファイルの読み込みとgeopandas

CSVファイルの読み込みとgeopandas

今回は品川区のオープンデータを使う

https://www.city.shinagawa.tokyo.jp/PC/kuseizyoho/digitaltransformation/opendate/index.html

品川だと避難所はCSVで提供されている
他にも公共施設
AED
交通事故などもある

とりあえず今回は公共施設のCSVを使うhttps://catalog.data.metro.tokyo.lg.jp/organization/t131091?q=公共施設&sort=score+desc%2C+metadata_modified+desc
で調べたら
https://www.city.shinagawa.tokyo.jp/ct/other000081600/kokyoshisetsu.csv
のリンクになった

import requests

url = "https://www.city.shinagawa.tokyo.jp/ct/other000081600/kokyoshisetsu.csv"
save_path = "kokyoshisetsu.csv"

r = requests.get(url)
r.raise_for_status()  # エラーなら例外を出す
with open(save_path, "wb") as f:
    f.write(r.content)

print(f"保存しました: {save_path}")

を実行してファイルをダウンロード

このデータを使うには
まず pandas にして次にgeodataflameにする

df = pd.read_csv('./kokyoshisetsu.csv',encoding='shift-jis')
df

これで

	施設名	施設名(英語)	カテゴリ	電話番号	郵便番号	住所	緯度	経度	説明(日本語)	説明(英語)	FAX番号	開所時間帯	開所時刻	閉所時刻	利用料金	画像URL	サムネイル画像URL	ホームページアドレス	創設日	創設者
0	品川区役所	Shinagawa City Office	区役所	03-3777-1111	140-0005	東京都品川区広町2-1-36	35.608837	139.73024	区役所	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
1	品川第一地域センター	Shinagawa Dai-ichi Community Ctr.	地域センター	03-3450-2000	140-0001	東京都品川区北品川3-11-16	35.616681	139.74008	地域センター	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
2	品川第二地域センター	Shinagawa Dai-ni Community Ctr.	地域センター	03-3472-2000	140-0004	東京都品川区南品川5-3-20	35.611373	139.74129	地域センター	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
3	大崎第一地域センター	Osaki Dai-ichi Community Ctr.	地域センター	03-3491-2000	141-0031	東京都品川区西五反田3-6-3	35.627776	139.71686	地域センター	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
4	大崎第二地域センター	Osaki Dai-ni Community Ctr.	地域センター	03-3492-2000	141-0032	東京都品川区大崎2-9-4	35.616945	139.72757	地域センター	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
753	なぎさ会館	Nagisa Funeral Hall	区民生活関連施設	03-5471-2700	140-0012	東京都品川区勝島3-1-3	35.596317	139.74173	葬祭施設	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
754	臨海斎場	NaN	区民生活関連施設	03-5755-2833	143-0001	東京都大田区東海1-3-1	35.585557	139.75282	葬祭施設	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
755	品川区消費者センター	Consumers' Ctr.	区民生活関連施設	03-5718-7181	140-0014	東京都品川区大井1-14-1 大井1丁目共同ビル4階	35.607696	139.73146	消費生活相談	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
756	男女共同参画センター	Equality of Gender Ctr.	区民生活関連施設	03-5479−4104	140-0011	東京都品川区東大井5−18−1品川区立総合区民会館3階	35.606177	139.73584	男女共同参画	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
757	品川区就業センター	NaN	区民生活関連施設	03-5498-6353	141-0033	東京都品川区西品川1-28-3	35.609295	139.72769	就業支援	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN	NaN
758 rows × 20 columns

となる

施設名から経度まではデータがあるが
それ以降はデータがないので読み込む部分を指定する

絞り込みは df.locを使う

しかし

df = df.loc[:,'施設名':'経度']

で何も表示されない

print(df.columns.tolist())

で調べたら

['施設名', '施設名(英語)', 'カテゴリ', '電話番号', '郵便番号', '住所', '緯度', '経度']

subset = df.loc[:, '施設名':'経度']
subset

としたら表示された

単純に最後にdfを表示しないだけだった

df = df.loc[:, '施設名':'経度']
df

施設名	施設名(英語)	カテゴリ	電話番号	郵便番号	住所	緯度	経度	
0	品川区役所	Shinagawa City Office	区役所	03-3777-1111	140-0005	東京都品川区広町2-1-36	35.608837	139.73024
1	品川第一地域センター	Shinagawa Dai-ichi Community Ctr.	地域センター	03-3450-2000	140-0001	東京都品川区北品川3-11-16	35.616681	139.74008
2	品川第二地域センター	Shinagawa Dai-ni Community Ctr.	地域センター	03-3472-2000	140-0004	東京都品川区南品川5-3-20	35.611373	139.74129
3	大崎第一地域センター	Osaki Dai-ichi Community Ctr.	地域センター	03-3491-2000	141-0031	東京都品川区西五反田3-6-3	35.627776	139.71686
4	大崎第二地域センター	Osaki Dai-ni Community Ctr.	地域センター	03-3492-2000	141-0032	東京都品川区大崎2-9-4	35.616945	139.72757
...	...	...	...	...	...	...	...	...
753	なぎさ会館	Nagisa Funeral Hall	区民生活関連施設	03-5471-2700	140-0012	東京都品川区勝島3-1-3	35.596317	139.74173
754	臨海斎場	NaN	区民生活関連施設	03-5755-2833	143-0001	東京都大田区東海1-3-1	35.585557	139.75282
755	品川区消費者センター	Consumers' Ctr.	区民生活関連施設	03-5718-7181	140-0014	東京都品川区大井1-14-1 大井1丁目共同ビル4階	35.607696	139.73146
756	男女共同参画センター	Equality of Gender Ctr.	区民生活関連施設	03-5479−4104	140-0011	東京都品川区東大井5−18−1品川区立総合区民会館3階	35.606177	139.73584
757	品川区就業センター	NaN	区民生活関連施設	03-5498-6353	141-0033	東京都品川区西品川1-28-3	35.609295	139.72769
758 rows × 8 columns

となった

次に各列に対してNaNがあるか調べる

df.isna().any()

結果は

施設名        False
施設名(英語)     True
カテゴリ       False
電話番号        True
郵便番号       False
住所         False
緯度         False
経度         False
dtype: bool

これでfalseになっているものは全て入っているのが確認できる
今回のデータは必要な
施設名
住所
緯度経度
が入っているのが確認できる

次にデータタイプを確認

type(df)

で識別すると

pandas.core.frame.DataFrame

これで 冤罪は pandas の dataflameになっているのがわかる

これを geopandasへ変換する

変換するには緯度経度を使ってジオメトリ列を作成する

このジオメトリ列を使って geodataflameへ変換する

まずはジオメトリ列の作成
これは

geometry = gpd.points_from_xy(df['経度'],df['緯度'])

で簡単にできる

<GeometryArray>
[ <POINT (139.73 35.609)>,  <POINT (139.74 35.617)>, <POINT (139.741 35.611)>,
 <POINT (139.717 35.628)>, <POINT (139.728 35.617)>, <POINT (139.738 35.596)>,
  <POINT (139.73 35.604)>, <POINT (139.721 35.598)>, <POINT (139.706 35.619)>,
 <POINT (139.704 35.611)>,
 ...
 <POINT (139.749 35.622)>, <POINT (139.736 35.614)>, <POINT (139.733 35.618)>,
 <POINT (139.714 35.614)>, <POINT (139.753 35.608)>, <POINT (139.742 35.596)>,
 <POINT (139.753 35.586)>, <POINT (139.731 35.608)>, <POINT (139.736 35.606)>,
 <POINT (139.728 35.609)>]
Length: 758, dtype: geometry

これを使って geodataflameへ変換する

gdf = gpd.GeoDataFrame(df,geometry=geometry)
gdf

これで

施設名	施設名(英語)	カテゴリ	電話番号	郵便番号	住所	緯度	経度	geometry	
0	品川区役所	Shinagawa City Office	区役所	03-3777-1111	140-0005	東京都品川区広町2-1-36	35.608837	139.73024	POINT (139.73024 35.60884)
1	品川第一地域センター	Shinagawa Dai-ichi Community Ctr.	地域センター	03-3450-2000	140-0001	東京都品川区北品川3-11-16	35.616681	139.74008	POINT (139.74008 35.61668)
2	品川第二地域センター	Shinagawa Dai-ni Community Ctr.	地域センター	03-3472-2000	140-0004	東京都品川区南品川5-3-20	35.611373	139.74129	POINT (139.74129 35.61137)
3	大崎第一地域センター	Osaki Dai-ichi Community Ctr.	地域センター	03-3491-2000	141-0031	東京都品川区西五反田3-6-3	35.627776	139.71686	POINT (139.71686 35.62778)
4	大崎第二地域センター	Osaki Dai-ni Community Ctr.	地域センター	03-3492-2000	141-0032	東京都品川区大崎2-9-4	35.616945	139.72757	POINT (139.72757 35.61694)
...	...	...	...	...	...	...	...	...	...
753	なぎさ会館	Nagisa Funeral Hall	区民生活関連施設	03-5471-2700	140-0012	東京都品川区勝島3-1-3	35.596317	139.74173	POINT (139.74173 35.59632)
754	臨海斎場	NaN	区民生活関連施設	03-5755-2833	143-0001	東京都大田区東海1-3-1	35.585557	139.75282	POINT (139.75282 35.58556)
755	品川区消費者センター	Consumers' Ctr.	区民生活関連施設	03-5718-7181	140-0014	東京都品川区大井1-14-1 大井1丁目共同ビル4階	35.607696	139.73146	POINT (139.73146 35.6077)
756	男女共同参画センター	Equality of Gender Ctr.	区民生活関連施設	03-5479−4104	140-0011	東京都品川区東大井5−18−1品川区立総合区民会館3階	35.606177	139.73584	POINT (139.73584 35.60618)
757	品川区就業センター	NaN	区民生活関連施設	03-5498-6353	141-0033	東京都品川区西品川1-28-3	35.609295	139.72769	POINT (139.72769 35.6093)
758 rows × 9 columns

というように
geometryが追加される

これでタイプを確認

type(gdf)

結果は

geopandas.geodataframe.GeoDataFrame

となって GeoDataflameになっているのが確認できる

これで
Csvファイルからジオメトリを作成し、それを元にGeoDataflameの作成ができた

さらに図で表示

gdf.plot()

山手線のプロット

山手線のプロット

路線図から山手線のみプロットする

gdf = gpd.read_file('./N02-19_GML/N02-19_RailroadSection.geojson')
gdf

	鉄道区分	事業者種別	路線名	運営会社	geometry
0	23	5	沖縄都市モノレール線	沖縄都市モノレール	LINESTRING (127.67948 26.21454, 127.6797 26.21...
1	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.29139 40.3374, 141.29176 40.33...
2	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.27554 40.23936, 141.27567 40.2...
3	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.28659 40.26092, 141.28538 40.2...
4	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.29082 40.28615, 141.29089 40.2...
...	...	...	...	...	...
22011	12	4	相鉄新横浜線	相模鉄道	LINESTRING (139.58522 35.48055, 139.58384 35.4...
22012	12	4	相鉄新横浜線	相模鉄道	LINESTRING (139.56671 35.47802, 139.56433 35.4...
22013	11	2	おおさか東線	西日本旅客鉄道	LINESTRING (135.56227 34.68802, 135.564 34.68798)
22014	11	2	東海道線	東日本旅客鉄道	LINESTRING (139.66375 35.57335, 139.66382 35.5...
22015	11	2	東海道線	東日本旅客鉄道	LINESTRING (139.58705 35.4819, 139.58646 35.48...

となっているので
路線名で調べれるかをテスト

gdf['路線名'].unique()

結果は

array(['沖縄都市モノレール線', 'いわて銀河鉄道線', 'いすみ線', '三国芦原線', '勝山永平寺線', '湯前線',
       'しなの鉄道線', '広島短距離交通瀬野線', '七尾線', 'わたらせ渓谷線', '阿佐東線', '阿武隈急行線',
       '愛知環状鉄道線', '東部丘陵線', '鞍馬山鋼索鉄道', '伊勢線', '伊豆急行線', '十国鋼索線', '駿豆線',
       '大雄山線', '横河原線', '花園線', '郡中線', '高浜線', '城南線', '城北線', '大手町線', '本町線',
       '井原線', '大社線', '北松江線', '鞍馬線', '叡山本線', '鉄道線', 'みなとみらい21線', '1号線',
       '3号線', '金沢シーサイドライン', '清輝橋線', '東山本線', '会津線', '岳南線', '甘木線', '常総線',
       '竜ヶ崎線', '紀州鉄道線', '井の頭線', '京王線', '競馬場線', '高尾線', '相模原線', '動物園線',
       '宇治線', '鴨東線', '京阪本線', '京津線', '交野線', '鋼索線', '石山坂本線', '押上線', '金町線',
       '千原線', '千葉線', '東成田線', '本線', '烏丸線', '東西線', '久里浜線', '空港線', '逗子線',
       '大師線', '北野線', '嵐山本線', '錦川清流線', '橿原線', '吉野線', '京都線', '御所線', '山田線',
       '志摩線', '信貴線', '生駒鋼索線', '生駒線', '西信貴鋼索線', '大阪線', '長野線', '鳥羽線', '天理線',
       '田原本線', '湯の山線', '道明寺線', '奈良線', '内部線', '南大阪線', '難波線', '阪神なんば線',
       '八王子線', '名古屋線', '鈴鹿線', '多賀線', '八日市線', '吉都線', '久大線', '宮崎空港線',
       '九州新幹線', '後藤寺線', '香椎線', '佐世保線', '三角線', '山陽線', '指宿枕崎線', '鹿児島線',
       '篠栗線', '大村線', '筑肥線', '筑豊線', '長崎線', '唐津線', '日田彦山線', '日南線', '日豊線',
       '肥薩線', '豊肥線', '幹線', '健軍線', '上熊本線', '水前寺線', '田崎線', '菊池線', '藤崎線',
       'ケーブルカー', '広島新交通1号線', '宇品線', '横川線', '皆実線', '宮島線', '江波線', '白島線',
       '弘南線', '大鰐線', '江ノ島電鉄線', '琴平線', '志度線', '長尾線', '高尾鋼索線', '嵯峨野観光線',
       '伊丹線', '甲陽線', '今津線', '神戸高速線', '神戸線', '千里線', '宝塚線', '箕面線', '嵐山線',
       '阪堺線', '上町線', '武庫川線', '埼玉高速鉄道線', '伊奈線', '1条線', '山鼻西線', '山鼻線',
       '東豊線', '南北線', '近鉄連絡線', '三岐線', '北勢線', 'リアス線', '釜石線', 'フラワー長井線',
       'ユーカリが丘線', '網干線', '八栗ケーブル', '高徳線', '土讃線', '徳島線', '内子線', '本四備讃線',
       '牟岐線', '鳴門線', '予讃線', '予土線', '第一期線', '第二期線', '谷山線', '唐湊線', '大洗鹿島線',
       '芝山鉄道線', '若桜線', '常磐新線', '秋田内陸線', '江ノ島線', '小田原線', '多摩線', '小湊鐵道線',
       '西九州線', '上高地線', '江の島線', '上信線', '別所線', '上毛線', '信楽線', '新京成線', '真岡線',
       '海岸線', '山手線', '西神延伸線', '西神線', '摩耶ケーブル線', '六甲アイランド線', '粟生線',
       '公園都市線', '三田線', '有馬線', '水間線', '水島本線', '貝塚線', '太宰府線', '天神大牟田線',
       'JR東西線', '因美線', '宇部線', '宇野線', '越美北線', '加古川線', '可部線', '関西空港線',
       '関西線', '岩徳線', '紀勢線', '吉備線', '境線', '芸備線', '湖西線', '呉線', '高山線', '阪和線',
       '桜井線', '桜島線', '山陰線', '山口線', '山陽新幹線', '小浜線', '小野田線', '城端線', '赤穂線',
       '草津線', '大阪環状線', '大糸線', '津山線', '東海道線', '播但線', '伯備線', '博多南線', '美祢線',
       '姫新線', '氷見線', '舞鶴線', '福塩線', '福知山線', '片町線', '日本海ひすいライン',
       '妙高はねうまライン', 'あいの風とやま鉄道線', 'IRいしかわ鉄道線', '北陸線', '木次線', '和歌山線',
       '狭山線', '国分寺線', '新宿線', '西武園線', '西武秩父線', '西武有楽町線', '多摩湖線', '多摩川線',
       '池袋線', '拝島線', '豊島線', '青い森鉄道線', '静岡清水線', '2号線', 'いずみ野線', '流山線',
       '多摩都市モノレール線', '井川線', '大井川本線', '国際文化公園都市モノレール線(彩都線)', '大阪モノレール線',
       '1号線(御堂筋線)', '2号線(谷町線)', '3号線(四つ橋線)', '4号線(中央線)', '5号線(千日前線)',
       '6号線(堺筋線)', '7号線(長堀鶴見緑地線)', '南港ポートタウン線', '泉北高速鉄道線', '大山鋼索線', '樽見線',
       '天橋立鋼索鉄道', '智頭線', '筑波山鋼索鉄道線', '筑豊電気鉄道線', '秩父本線', '銚子電気鉄道線',
       '蛍茶屋支線', '桜町支線', '赤迫支線', '大浦支線', '越美南線', '津軽鉄道線', '天竜浜名湖線', '阿佐線',
       '宿毛線', '中村線', '伊野線', '後免線', '桟橋線', '駅前線', '島原鉄道線', '御殿場線', '参宮線',
       '身延線', '太多線', '中央線', '東海道新幹線', '飯田線', '武豊線', '名松線', '東京モノレール羽田線',
       'こどもの国線', '世田谷線', '大井町線', '池上線', '田園都市線', '東横線', '東急多摩川線', '目黒線',
       '2号線日比谷線', '3号線銀座線', '4号線丸ノ内線', '4号線丸ノ内線分岐線', '5号線東西線', '7号線南北線',
       '8号線有楽町線', '13号線副都心線', '9号線千代田線', '11号線半蔵門線', '1号線浅草線', '6号線三田線',
       '10号線新宿線', '12号線大江戸線', '荒川線', '上野懸垂線', 'りんかい線', '伊東線', '烏山線',
       '羽越線', '越後線', '奥羽線', '横須賀線', '横浜線', '花輪線', '外房線', '気仙沼線', '久留里線',
       '京葉線', '五日市線', '五能線', '吾妻線', '高崎線', '根岸線', '左沢線', '鹿島線', '篠ノ井線',
       '小海線', '上越新幹線', '上越線', '常磐線', '信越線', '北しなの線', '水郡線', '水戸線', '成田線',
       '青梅線', '石巻線', '赤羽線(埼京線)', '仙山線', '仙石線', '川越線', '相模線', '総武線',
       '大船渡線', '大湊線', '只見線', '男鹿線', '津軽線', '鶴見線', '田沢湖線', '東金線', '東北新幹線',
       '東北線', '東北線(埼京線)', '内房線', '南武線', '日光線', '白新線', '八戸線', '八高線', '飯山線',
       '磐越西線', '磐越東線', '武蔵野線', '米坂線', '北上線', '北陸新幹線', '弥彦線', '陸羽西線',
       '陸羽東線', '両毛線', '伊勢崎線', '宇都宮線', '越生線', '鬼怒川線', '亀戸線', '桐生線', '佐野線',
       '小泉線', '東上本線', '野田線', '東葉高速線', '高森線', '加太線', '高師浜線', '高野線', '多奈川線',
       '南海本線', '和歌山港線', '日生線', '妙見線', '大森線', '湯の川線', '宝来・谷地頭線',
       '皿倉山ケーブルカー', '比叡山鉄道線', '肥薩おれんじ鉄道線', '安野屋線', '呉羽線', '支線', '上滝線',
       '不二越線', '立山線', '河口湖線', '大月線', 'ディズニーリゾートライン', '福武線', '1号線(空港線)',
       '2号線(箱崎線)', '3号線(七隈線)', '飯坂線', '伊田線', '糸田線', '田川線', '渥美線', '東田本線',
       'ほくほく線', '海峡線', '釧網線', '道南いさりび鉄道線', '根室線', '札沼線', '室蘭線', '宗谷線',
       '石勝線', '石北線', '千歳線', '日高線', '函館線', '富良野線', '留萌線', '宮津線', '宮福線',
       '小倉線', '北条線', '北神線', '北総線', '石川線', '浅野川線', '高岡軌道線', '新湊港線',
       'ガイドウェイバス志段味線', '1号線東山線', '2号線名港線', '2号線名城線', '3号線鶴舞線', '4号線名城線',
       '6号線桜通線', '上飯田線', '羽島線', '河和線', '各務原線', '蒲郡線', '犬山線', '広見線', '三河線',
       '小牧線', '常滑線', '瀬戸線', '西尾線', '知多新線', '築港線', '竹鼻線', '津島線', '尾西線',
       '豊川線', '豊田線', '名古屋本線', '西名古屋港線', '明知線', '会津鬼怒川線', '鳥海山ろく線',
       '無軌条電車線', '六甲ケーブル線', '貴志川線', '東京臨海新交通臨海線', 'けいはんな線', 'ポートアイランド線',
       '富山港線', '仙台空港線', '8号線(今里筋線)', '中之島線', '富山都心線', '門司港レトロ観光線', '4号線',
       '日暮里・舎人線', 'おおさか東線', '湊線', '成田空港線', '養老線', '別府ラクテンチケーブル線', '伊賀線',
       '青函トンネル竜飛斜坑線', '富山駅南北接続線', '都心線', '北海道新幹線', '相鉄新横浜線'], dtype=object)

となる

この中から
山手線
が入っているかを true falseで調べる

'山手線' in gdf['路線名'].unique()

結果は

True

なので山手線に絞って表示する

gdf_yamanote = gdf[gdf['路線名']=='山手線']
gdf_yamanote

結果は

	鉄道区分	事業者種別	路線名	運営会社	geometry
6631	12	3	山手線	神戸市	LINESTRING (135.14601 34.65849, 135.1453 34.65...
6632	12	3	山手線	神戸市	LINESTRING (135.19571 34.70498, 135.19561 34.7...
6633	12	3	山手線	神戸市	LINESTRING (135.15161 34.66859, 135.15099 34.6...
6634	12	3	山手線	神戸市	LINESTRING (135.19273 34.69404, 135.1921 34.69...
6635	12	3	山手線	神戸市	LINESTRING (135.18364 34.69074, 135.18351 34.6...
...	...	...	...	...	...
14500	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71054 35.64593, 139.71079 35.6...
14501	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71334 35.64093, 139.71343 35.6...
14502	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71438 35.63865, 139.71442 35.6...
14503	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71438 35.63865, 139.71447 35.6...
14504	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71499 35.63644, 139.71502 35.6...

これだと神戸のものも含まれるため
運営会社も絞り込みする

gdf_yamanote = gdf[(gdf['路線名']=='山手線') & (gdf['運営会社']=='東日本旅客鉄道')]
gdf_yamanote

とすれば

鉄道区分	事業者種別	路線名	運営会社	geometry	
14455	11	2	山手線	東日本旅客鉄道	LINESTRING (139.74144 35.73419, 139.74435 35.7...
14456	11	2	山手線	東日本旅客鉄道	LINESTRING (139.7585 35.74006, 139.759 35.7396...
14457	11	2	山手線	東日本旅客鉄道	LINESTRING (139.74914 35.73738, 139.74937 35.7...
14458	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70915 35.72618, 139.70802 35.7...
14459	11	2	山手線	東日本旅客鉄道	LINESTRING (139.7105 35.72903, 139.70915 35.72...
14460	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71167 35.73152, 139.7105 35.72...
14461	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72695 35.73203, 139.72422 35.7...
14462	11	2	山手線	東日本旅客鉄道	LINESTRING (139.76067 35.73826, 139.76248 35.7...
14463	11	2	山手線	東日本旅客鉄道	LINESTRING (139.74144 35.73419, 139.73926 35.7...
14464	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70423 35.7141, 139.70418 35.71...
14465	11	2	山手線	東日本旅客鉄道	LINESTRING (139.74697 35.73634, 139.74909 35.7...
14466	11	2	山手線	東日本旅客鉄道	LINESTRING (139.7066 35.72145, 139.70596 35.71...
14467	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72893 35.73154, 139.72695 35.7...
14468	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70596 35.71933, 139.70582 35.7...
14469	11	2	山手線	東日本旅客鉄道	LINESTRING (139.73926 35.73325, 139.73863 35.7...
14470	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70024 35.68926, 139.70034 35.6...
14471	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70176 35.68735, 139.70222 35.6...
14472	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70019 35.70011, 139.70006 35.6...
14473	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70129 35.65907, 139.70124 35.6...
14474	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70125 35.6879, 139.70148 35.68...
14475	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70295 35.6722, 139.70345 35.67...
14476	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70004 35.69167, 139.70024 35.6...
14477	11	2	山手線	東日本旅客鉄道	LINESTRING (139.7006 35.68995, 139.70004 35.69...
14478	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70125 35.6879, 139.70091 35.68...
14479	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70203 35.6845, 139.70239 35.68...
14480	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70239 35.68309, 139.70255 35.6...
14481	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70032 35.70175, 139.70019 35.7...
14482	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70251 35.67042, 139.70264 35.6...
14483	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70068 35.69107, 139.70116 35.6...
14484	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70342 35.71171, 139.70298 35.7...
14485	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70222 35.68555, 139.70272 35.6...
14486	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72888 35.61912, 139.72761 35.6...
14487	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71054 35.64593, 139.70971 35.6...
14488	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72761 35.62077, 139.72628 35.6...
14489	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71334 35.64093, 139.71404 35.6...
14490	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70971 35.64743, 139.70956 35.6...
14491	11	2	山手線	東日本旅客鉄道	LINESTRING (139.70221 35.65723, 139.70201 35.6...
14492	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72436 35.62526, 139.72422 35.6...
14493	11	2	山手線	東日本旅客鉄道	LINESTRING (139.73881 35.62549, 139.73883 35.6...
14494	11	2	山手線	東日本旅客鉄道	LINESTRING (139.73762 35.62049, 139.73764 35.6...
14495	11	2	山手線	東日本旅客鉄道	LINESTRING (139.7328 35.61677, 139.73342 35.61...
14496	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72888 35.61912, 139.7295 35.61...
14497	11	2	山手線	東日本旅客鉄道	LINESTRING (139.73863 35.627, 139.73844 35.62927)
14498	11	2	山手線	東日本旅客鉄道	LINESTRING (139.72262 35.62727, 139.72231 35.6...
14499	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71636 35.63136, 139.71585 35.6...
14500	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71054 35.64593, 139.71079 35.6...
14501	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71334 35.64093, 139.71343 35.6...
14502	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71438 35.63865, 139.71442 35.6...
14503	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71438 35.63865, 139.71447 35.6...
14504	11	2	山手線	東日本旅客鉄道	LINESTRING (139.71499 35.63644, 139.71502 35.6...

となr

gdf_yamanote.plot()

で表示すると

となる
環状でないのは、運営会社が別のところになっているかららしい

x軸の数値を表示を綺麗にするなら

fig = plt.figure(figsize=(9,6))
ax = fig.add_subplot(1,1,1)
gdf_yamanote.plot(ax=ax)

とすれば

というようにx軸の表示が潰れない

これは応用で東京都の県境とか市区町村の境界データを持ってきて
一緒に表示することもできる

次はCSVファイルの読み込み

ダウンロードしたシェープファイルの読み込み

ダウンロードしたシェープファイルの読み込み

今回はオープンデータの鉄道路線を使う
https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v2_3.html

今回は令和元年のデータをダウンロードして使う

Zipをダウンロードして解凍

N02-19_GML
がファイル名になるので

mv ~/Downloads/N02-19_GML .

で移動

今回使用するデータは路線図なので
RailroadSection.shp を使う

gdf = gpd.read_file('./N02-19_GML/N02-19_RailroadSection.shp',encoding='shift-jis')
gdf

で読み込んで表示

	N02_001	N02_002	N02_003	N02_004	geometry
0	23	5	沖縄都市モノレール線	沖縄都市モノレール	LINESTRING (127.67948 26.21454, 127.6797 26.21...
1	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.29139 40.3374, 141.29176 40.33...
2	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.27554 40.23936, 141.27567 40.2...
3	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.28659 40.26092, 141.28538 40.2...
4	12	5	いわて銀河鉄道線	アイジーアールいわて銀河鉄道	LINESTRING (141.29082 40.28615, 141.29089 40.2...
...	...	...	...	...	...
22011	12	4	相鉄新横浜線	相模鉄道	LINESTRING (139.58522 35.48055, 139.58384 35.4...
22012	12	4	相鉄新横浜線	相模鉄道	LINESTRING (139.56671 35.47802, 139.56433 35.4...
22013	11	2	おおさか東線	西日本旅客鉄道	LINESTRING (135.56227 34.68802, 135.564 34.68798)
22014	11	2	東海道線	東日本旅客鉄道	LINESTRING (139.66375 35.57335, 139.66382 35.5...
22015	11	2	東海道線	東日本旅客鉄道	LINESTRING (139.58705 35.4819, 139.58646 35.48...
22016 rows × 5 columns

あとは

gdf.plot()

とすると路線図が表示される

なおgeojsonでも同じ結果になるらしい

一応実験する
しかし
拡張子を .geojsonにしたら

---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
Cell In[10], line 1
----> 1 gdf = gpd.read_file('./N02-19_GML/N02-19_RailroadSection.geojson',encoding='shift-jis')
      2 gdf

File ~/.pyenv/versions/3.11.0/lib/python3.11/site-packages/geopandas/io/file.py:316, in _read_file(filename, bbox, mask, columns, rows, engine, **kwargs)
    313             filename = response.read()
    315 if engine == "pyogrio":
--> 316     return _read_file_pyogrio(
    317         filename, bbox=bbox, mask=mask, columns=columns, rows=rows, **kwargs
    318     )
    320 elif engine == "fiona":
    321     if pd.api.types.is_file_like(filename):

File ~/.pyenv/versions/3.11.0/lib/python3.11/site-packages/geopandas/io/file.py:576, in _read_file_pyogrio(path_or_bytes, bbox, mask, rows, **kwargs)
    567     warnings.warn(
    568         "The 'include_fields' and 'ignore_fields' keywords are deprecated, and "
    569         "will be removed in a future release. You can use the 'columns' keyword "
   (...)    572         stacklevel=3,
    573     )
    574     kwargs["columns"] = kwargs.pop("include_fields")
--> 576 return pyogrio.read_dataframe(path_or_bytes, bbox=bbox, **kwargs)

File ~/.pyenv/versions/3.11.0/lib/python3.11/site-packages/pyogrio/geopandas.py:275, in read_dataframe(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, fid_as_index, use_arrow, on_invalid, arrow_to_pandas_kwargs, **kwargs)
    270 if not use_arrow:
    271     # For arrow, datetimes are read as is.
    272     # For numpy IO, datetimes are read as string values to preserve timezone info
    273     # as numpy does not directly support timezones.
    274     kwargs["datetime_as_string"] = True
--> 275 result = read_func(
    276     path_or_buffer,
    277     layer=layer,
    278     encoding=encoding,
    279     columns=columns,
    280     read_geometry=read_geometry,
    281     force_2d=gdal_force_2d,
    282     skip_features=skip_features,
    283     max_features=max_features,
    284     where=where,
    285     bbox=bbox,
    286     mask=mask,
    287     fids=fids,
    288     sql=sql,
    289     sql_dialect=sql_dialect,
    290     return_fids=fid_as_index,
    291     **kwargs,
    292 )
    294 if use_arrow:
    295     import pyarrow as pa

File ~/.pyenv/versions/3.11.0/lib/python3.11/site-packages/pyogrio/raw.py:198, in read(path_or_buffer, layer, encoding, columns, read_geometry, force_2d, skip_features, max_features, where, bbox, mask, fids, sql, sql_dialect, return_fids, datetime_as_string, **kwargs)
     59 """Read OGR data source into numpy arrays.
     60 
     61 IMPORTANT: non-linear geometry types (e.g., MultiSurface) are converted
   (...)    194 
    195 """
    196 dataset_kwargs = _preprocess_options_key_value(kwargs) if kwargs else {}
--> 198 return ogr_read(
    199     get_vsi_path_or_buffer(path_or_buffer),
    200     layer=layer,
    201     encoding=encoding,
    202     columns=columns,
    203     read_geometry=read_geometry,
    204     force_2d=force_2d,
    205     skip_features=skip_features,
    206     max_features=max_features or 0,
    207     where=where,
    208     bbox=bbox,
    209     mask=_mask_to_wkb(mask),
    210     fids=fids,
    211     sql=sql,
    212     sql_dialect=sql_dialect,
    213     return_fids=return_fids,
    214     dataset_kwargs=dataset_kwargs,
    215     datetime_as_string=datetime_as_string,
    216 )

File pyogrio/_io.pyx:1344, in pyogrio._io.ogr_read()

File pyogrio/_io.pyx:668, in pyogrio._io.get_fields()

File pyogrio/_ogr.pyx:28, in pyogrio._ogr.get_string()

UnicodeDecodeError: 'shift_jis' codec can't decode byte 0x84 in position 2: illegal multibyte sequence

となった

ファイルが GeoJSON の場合 → 文字コードは 必ず UTF-8 です。
encoding=”shift-jis” を指定すると逆に壊れてしまいます。
N02-19_RailroadSection は国交省の N02 鉄道データですが、配布形式は
* Shapefile (.shp) → 属性が Shift-JIS
* GeoJSON (.geojson) → UTF-8

GeoJSON なら encoding 指定は不要
Shapefile の場合は属性に Shift-JIS が使われている

gdf = gpd.read_file('./N02-19_GML/N02-19_RailroadSection.geojson')
gdf

で成功

次に表示だが小さいと見えにくいので拡大する

fig = plt.figure(figsize=(8,10))
ax = fig.add_subplot(1,1,1)
gdf.plot(ax=ax)

実行すると路線図が拡大表示される

geopandas関連

PythonでのGIS・位置情報データのハンドリング実践

ShapelyやGeoPandas、Folium、Plotlyなどによるさまざまなデータ処理・可視化手法を学ぶ

OSMNXはopenstreetmap による最短経路の算出

内容の実践はdocker を使ってるけど
Pipでインストールも可能

Esri japanでGISの活用事例が載っている

今回はpythonでやるのでQGISは使わない

シェープファイルのサイズはそれぞれ2GBまで
フィールド名は英数字10文字
日本語なら5文字まで

geopandas関連

 pip install geopandas

でインストール

次に地図関連

pip install folium

pip install matplotlib

で可視化ツールのインストール

pip install jupyter

でインストール

jupyter-notebook   

で起動

以下コード

import geopandas as gpd
import folium
import matplotlib.pyplot as plt

でインポート

path = gpd.datasets.get_path('nybb')
gdf = gpd.read_file(path)
gdf.head()

でデータ取得して表示するはずが

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 1
----> 1 path = gpd.datasets.get_path('nybb')
      2 gdf = gpd.read_file(path)
      3 gdf.head()

File ~/.pyenv/versions/3.11.0/lib/python3.11/site-packages/geopandas/datasets/__init__.py:18, in get_path(dataset)
     12 error_msg = (
     13     "The geopandas.dataset has been deprecated and was removed in GeoPandas "
     14     f"1.0. You can get the original '{dataset}' data from "
     15     f"{ne_message if 'natural' in dataset else nybb_message}"
     16 )
     17 if dataset in _prev_available:
---> 18     raise AttributeError(error_msg)
     19 else:
     20     error_msg = (
     21         "The geopandas.dataset has been deprecated and "
     22         "was removed in GeoPandas 1.0. New sample datasets are now available "
     23         "in the geodatasets package (https://geodatasets.readthedocs.io/en/latest/)"
     24     )

AttributeError: The geopandas.dataset has been deprecated and was removed in GeoPandas 1.0. You can get the original 'nybb' data from the geodatasets package.

from geodatasets import get_path
path_to_file = get_path('nybb')

原因は
GeoPandas 1.0 以降では gpd.datasets.get_path(‘nybb’) が削除された ため
代わりに、geodatasets パッケージを使う必要がある

pip install geodatasets

でインストール

インポート文を

import geopandas as gpd
import folium
import matplotlib.pyplot as plt
from geodatasets import get_path 

にして

# path = gpd.datasets.get_path('nybb')
# データセットの取得
path = get_path("nybb")
gdf = gpd.read_file(path)
gdf.head()

これで

Downloading file 'nybb_16a.zip' from 'https://www.nyc.gov/assets/planning/download/zip/data-maps/open-data/nybb_16a.zip' to '/Users/snowpool/Library/Caches/geodatasets'.
Extracting 'nybb_16a/nybb.shp' from '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip' to '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip.unzip'
Extracting 'nybb_16a/nybb.shx' from '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip' to '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip.unzip'
Extracting 'nybb_16a/nybb.dbf' from '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip' to '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip.unzip'
Extracting 'nybb_16a/nybb.prj' from '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip' to '/Users/snowpool/Library/Caches/geodatasets/nybb_16a.zip.unzip'
	BoroCode	BoroName	Shape_Leng	Shape_Area	geometry
0	5	Staten Island	330470.010332	1.623820e+09	MULTIPOLYGON (((970217.022 145643.332, 970227....
1	4	Queens	896344.047763	3.045213e+09	MULTIPOLYGON (((1029606.077 156073.814, 102957...
2	3	Brooklyn	741080.523166	1.937479e+09	MULTIPOLYGON (((1021176.479 151374.797, 102100...
3	1	Manhattan	359299.096471	6.364715e+08	MULTIPOLYGON (((981219.056 188655.316, 980940....
4	2	Bronx	464392.991824	1.186925e+09	MULTIPOLYGON (((1012821.806 229228.265, 101278...

というように無事に表示される

次に表ではなく図で描画

gdf.plot()

で簡単に描画できる

次に座標情報を表示

gdf.crs

次に座標系の変換

gdf = gdf.to_crs(epsg=4326)
print(gdf.crs)
gdf.head()

これで座標が緯度経度に変わっている

EPSG:4326
	BoroCode	BoroName	Shape_Leng	Shape_Area	geometry
0	5	Staten Island	330470.010332	1.623820e+09	MULTIPOLYGON (((-74.05051 40.56642, -74.05047 ...
1	4	Queens	896344.047763	3.045213e+09	MULTIPOLYGON (((-73.83668 40.59495, -73.83678 ...
2	3	Brooklyn	741080.523166	1.937479e+09	MULTIPOLYGON (((-73.86706 40.58209, -73.86769 ...
3	1	Manhattan	359299.096471	6.364715e+08	MULTIPOLYGON (((-74.01093 40.68449, -74.01193 ...
4	2	Bronx	464392.991824	1.186925e+09	MULTIPOLYGON (((-73.89681 40.79581, -73.89694 ...

緯度経度に変換すれば foliumで地図表示ができる

まずは地図の表示

m = folium.Map(location=[40.7,-73.96],zoom_start=10,tiles='CaltoDB positiuon')
m

としたらエラー

単純に

CartoDB positron

の間違い

m = folium.Map(location=[40.7,-73.96],zoom_start=10,tiles='CartoDB positron')
m

が正解

次に地図上にgeopandas情報を描画する
まずはこの情報を上から見ていくので

for __, r in gdf.iterrows():
    sim_geo = gpd.GeoSeries(r['geometory']).simplify(tolerance=0.001)
    geo_j = sim_geo.to_json()
    geo_j = folium.GeoJson(data=geo_j,style_function=lambda x:{'fillColor':'orange'})
    folium.Popup(r['BoroName']).add_to(geo_j)
    geo_j.add_to(m)
m

今回もエラー

原因は今回もスペルミス

geometory

じゃなくて

geometry

が正解

これで地図の上に情報が表示される

geopandas関連その2

今回は shapely を使う

import pandas as pd
import geopandas as gpd
import matplotlib as plt
from shapely.geometry import Point,LineString, Polygon

まずはPoint の練習

#Point
point = Point(0.0,1.0)
point

これで座標を指定するとそこに⭕️が表示される

この座標を出すなら

print(point)

とすれば

POINT (0 1)

と座標を表示できる

ジオメトリタイプを調べるなら

point.geom_type

というように

.geom_type

をつけることでタイプの判別ができる

次にLineStringの練習

line = LineString([(0,0),(1,1),(3,0),(5,2)])
line

これで折れ線が表示される

print(line)

LINESTRING (0 0, 1 1, 3 0, 5 2)

というように配列の座標を見れる

次はPolygon

polygon = Polygon([(0,0),(1,1),(3,0)])
polygon

これで三角形が表示される

polygon = Polygon([(0,0),(0,1),(1,1),(1,0)])
polygon

これで四角形

これらをまとめて行うのがジオメトリコレクション

#multipoint
from shapely.geometry import MultiPoint,MultiLineString,MultiPolygon

で必要なインポートを行う

points = MultiPoint([(0,0),(1,0)])
points

で2つの座標に⭕️が表示される

coords = [((0,0),(1,1)),((-1,0),(1,0))]
lines = MultiLineString(coords)
lines

これで2つの線が憑依される

a = Polygon([(0,0),(0,1),(1,1),(1,0)])
b =Polygon([(2,0),(2,1),(3,1),(3,0)])
polygons = MultiPolygon([a,b])
polygons

で2つの四角が表示される

次に LineString の長さ

l = LineString([(1,-1),(1,0),(2,0),(2,1)])
l

この長さを求めるには
.length を使う

これは道路などで使うことになる

次に polygonの面積

p=Polygon([(3,-1),(4,0),(3,1)])
p

で三角形

面積は
areaで求めることができるので

p.area

とすれば

1.0

となる

次にbuffer
これは Point LineString に幅を与えてpolygonにするもの

s = gpd.GeoSeries(
    [
        Point(0,0),
        LineString([(1,-1),(1,0),(2,0),(2,1)]),
        Polygon([(3,-1),(4,0),(3,1)])
    ])
s

結果は

0                         POINT (0 0)
1    LINESTRING (1 -1, 1 0, 2 0, 2 1)
2    POLYGON ((3 -1, 4 0, 3 1, 3 -1))
dtype: geometry

次に

s.buffer(0.5)

とすると

0    POLYGON ((0.5 0, 0.49759 -0.04901, 0.49039 -0....
1    POLYGON ((0.5 0, 0.50241 0.04901, 0.50961 0.09...
2    POLYGON ((2.5 -1, 2.5 1, 2.50241 1.04901, 2.50...
dtype: geometry

s.buffer(0.5)[0]

で円ができる
これは半径0.5の円

次にLineString での buffer

s.buffer(0.5)[1]

これで線の周りが追加されて図みたいになっている
この時に角はまるくなる

同様にpolygonも試す

s.buffer(0.5)[2]

これで三角形の角がまるくなる

これらは後々、ジオメトリの空間結合で使う
例えば予測地点の100m以内のみ表示などで使う

RAGのメイン処理

RAGのメイン処理

    # RAG検索
    query_embed = ollama_embed(prompt)
    results = st.session_state.collection.query(
        query_embeddings=[query_embed],
        n_results=2
    )
query_embed = ollama_embed(prompt)

でユーザのプロンプトをベクトル化

session_state.collection.query

を使うことで
chromaDBのメソッドを使うことで
似ている情報をベクトル同士を比べることで似ている情報を得ることが可能になる

この中で

query_embeddings=[query_embed],

でプロンプトを指定して

n_results=2

で上位何個を該当させるか指定できる
今回は上位2つまで

このときの結果は

    # {
    #     "ids":
    #     "documents": [["doc1", "doc2"]]
    #     "distances": [[XXX, XXX]]
    # }

というように返ってくる
Documents はテキスト内容
Distancesはベクトルの数値
なので必要なのは documentsの値になるので
これがあるかで判定すればいい

なので
Documentsの0番目のインデックスを取得すれば
最初のリストが取得できる

あとは中身を joinで結合すればいいので

    if results["documents"]:
        context_text = "\n".join(results["documents"][0])

というようにして
変数に結果を格納する

次に、RAG部分のプロンプトの作成

        rag_prompt = f"""
        以下は関連ドキュメントの抜粋です。
        {context_text}
        この情報を参考に以下の質問に答えてください。
        {prompt}
        """

こうすることで
質問を入力すれば、事前に参照するプロンプトが既に組み込まれているので
簡単にDB参照の機能が追加された状態で質問が構築される

もし、documentsがない、つまり該当する知識がないのなら

        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

というようにすれば分岐処理になって
RAGなしのプロンプトが実行されるようになる

あとは

    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

で情報を履歴として保持するようにする

あとは
システムプロンプトを入れて過去の履歴を追加する

    if system_prompt.strip():
        messages = [{"role": "system", "content": system_prompt}] + st.session_state.messages
    else:
        messages = st.session_state.messages
    

これで RAGを使ったmessages にプロンプトが格納される

それを ollamaに渡す

    # LLMの返答を表示
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            stream=True
        )
        for chunk in stream:
            stream_response += chunk.choices[0].delta.content
            placeholder.write(stream_response)

これで streamlitで回答を表示
回答内容を保存もしている

ファイルをベクトル化してDBへ保存

ファイルをベクトル化してDBへ保存

if st.sidebar.button("インデックス作成"):
    for file in uploaded_files:
        text = load_word_document(file)
        chunks = split_text(text)
        for i, chunk in enumerate(chunks):
            embed = ollama_embed(chunk)
            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )
    st.sidebar.success("インデックス作成完了")

によって
ボタンを押したら処理が完了する

if st.sidebar.button("インデックス作成"):

はボタンを押したら、という意味

uploaded_files:

は自作のメソッドで、ファイルアップロードしたときの結果が入っているので

for file in uploaded_files:

でfor ループで file へ代入していく

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

がそのメソッド

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

も自作のメソッドで

 text = load_word_document(file)

によってアップされたwordファイルの中身を textへ代入

テキストの分割は

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

で行っているので

chunks = split_text(text)

でチャンクに分割したものを chunks へ格納

for i, chunk in enumerate(chunks):

でインデックスと一緒に代入していく

そして自作したベクトル化関数

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

これを使い

embed = ollama_embed(chunk)

でベクトル化したものを変数に格納

            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )


documents=[chunk],は元々の文章
embeddings=,はベクトル化したもの
ids=[f”{file.name}_{i}”]はアップロードしたファイル名とインデックス番号
となる

これでアップしたword ファイルの内容をベクトル化しDBへ保存する仕組みができた

次はインプットした情報をベクトル変換し
そのベクトルと近い情報をDBから撮ってきて回答を得る仕組みを作る

RAGで使う wordファイルのアップローダ作成

RAGで使う wordファイルのアップローダ作成

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

これで

type=["docx"],

の設定で拡張子が docx のもののみ受け付ける

  accept_multiple_files=True

これで複数ファイルのアップロードが可能

uploaded_files =

とすることで機能を変数に格納できる

RAGのためのテキスト分割関数

RAGのためのテキスト分割関数

文章となる文字列をチャンクという塊に分割し
分けたものをベクトル化してDBに入れる

このためチャンクに分ける分割関数を作成する

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

以下解説

chunk_size = 200

チャンクサイズの設定

overlap = 50

塊の許容範囲の設定

これにより1000文字なら
最初の200文字をチャンクにして
次のチャンクは151〜350が1つのチャンクになる

Overlapは重なりの意味なので
50被っているようになる

    start = 0
    while start < len(text):


Startがテキストの文字数より小さい間はループを続ける

end = start + chunk_size

が終了地点の設定
つまりチャンクサイズの200より上になれば終わり

 chunks.append(text[start:end])

で文字を

 chunks = []

append(text[start:end])

によって追加していく

そして

  start += chunk_size - overlap

で次のスタート地点の設定

chunk_size - overlap

の計算で
200−50
となって
150から開始地点となる

最後に

   return chunks

で分割されたチャンクを返すようにして処理が終わる

chromaDB

ローカルLLMを使ってRAGが導入された本格的なチャットボットを作ろう!ローカルLLM実行ツールであるOllamaとPythonを使ってローカル環境にいくら実行しても無料のAI環境を作ろう!

で word ファイルをRAGにしているので参考になるかも
chromaDBも取り扱っているし

https://ollama.com/library/nomic-embed-text
でollamaでベクトル化するモデルをインストールする

ollama pull nomic-embed-text

でインストール

次にchromaDBのインストール

pip install chromadb

なお
ChromaDBでは、テキストデータを直接ベクトルストアに保存することもできる
ChromaDBをローカルファイルで使う

も参考に

そしてドキュメントファイルをインストールして使えるようにする
python-docxと request もインストールする

pip install python-docx  

python-docx でWordファイルを操作する
も参考に

とりあえず
OpenAI のAPIなので
後でOllamaに書き換えて行う

import streamlit as st
from openai import OpenAI
import chromadb
from docx import Document
import requests


# ChromaDBの設定
DB_DIR = "./chroma_db"
chroma_client = chromadb.PersistentClient(path=DB_DIR)

if "collection" not in st.session_state:
    st.session_state.collection = chroma_client.get_or_create_collection(
        name="local_docs"
    )

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

# テキスト分割関数
def split_text(text):
    chunk_size = 200
    overlap = 50
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

## サイドバーの設定 ##
st.set_page_config(page_title="Local LLM Chat")

st.sidebar.title("設定")
model = st.sidebar.text_input("モデル名", value="llama3.1:8b")
temperature = st.sidebar.slider("temperature", 0.0, 2.0, 0.3, 0.1)
system_prompt = st.sidebar.text_area(
    "System Prompt",
    "あなたは有能なアシスタントです。日本語で回答してください",
)

# ワードファイルのアップロード
uploaded_files = st.sidebar.file_uploader(
    "Wordファイルをアップロード(.docx)",
    type=["docx"],
    accept_multiple_files=True
)

if st.sidebar.button("インデックス作成"):
    for file in uploaded_files:
        text = load_word_document(file)
        chunks = split_text(text)
        for i, chunk in enumerate(chunks):
            embed = ollama_embed(chunk)
            st.session_state.collection.add(
                documents=[chunk],
                embeddings=,
                ids=[f"{file.name}_{i}"]
            )
    st.sidebar.success("インデックス作成完了")

# タイトル
st.title("Local LLM Chat")

# 会話の履歴を保管
if "messages" not in st.session_state:
    st.session_state.messages = []

# 会話の履歴をリセットするボタン
if st.sidebar.button("会話をリセット"):
    st.session_state.messages = []

# 会話の履歴を表示
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.write(m["content"])


prompt = st.chat_input("メッセージを入力")

client = OpenAI(
    api_key="ollama",   
    base_url="http://localhost:12000/v1"
)

if prompt:

    # ユーザーのプロンプトを表示
    with st.chat_message("user"):
        st.write(prompt)

    # RAG検索
    query_embed = ollama_embed(prompt)
    results = st.session_state.collection.query(
        query_embeddings=[query_embed],
        n_results=2
    )

    # {
    #     "ids":
    #     "documents": [["doc1", "doc2"]]
    #     "distances": [[XXX, XXX]]
    # }

    if results["documents"]:
        context_text = "\n".join(results["documents"][0])
        rag_prompt = f"""
        以下は関連ドキュメントの抜粋です。
        {context_text}
        この情報を参考に以下の質問に答えてください。
        {prompt}
        """
        final_user_prompt = rag_prompt
    else:
        final_user_prompt = prompt

    st.session_state.messages.append({"role": "user", "content": final_user_prompt})

    if system_prompt.strip():
        messages = [{"role": "system", "content": system_prompt}] + st.session_state.messages
    else:
        messages = st.session_state.messages
    
    # LLMの返答を表示
    with st.chat_message("assistant"):
        placeholder = st.empty()
        stream_response = ""
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            temperature=temperature,
            stream=True
        )
        for chunk in stream:
            stream_response += chunk.choices[0].delta.content
            placeholder.write(stream_response)

    # 会話の履歴を保存
    
    st.session_state.messages.append({"role": "assistant", "content": stream_response})

がコードの内容

以下は解説
まず最初にchromaDBの設定をする

DB_DIR = "./chroma_db"


この指定フォルダにDBを保存する

chroma_client = chromadb.PersistentClient(path=DB_DIR)

でchromaDBのDBをセット

この2つで使う準備ができる

if "collection" not in st.session_state: st.session_state.collection = chroma_client.get_or_create_collection( name="local_docs" )

でDBにテーブルがなければ作成

ここから必要になる機能を作成する
作成する機能は
ベクトル化関数
Wordファイルを読み込む関数
テキスト分割関数
がRAG必要

# Ollamaからインストールしたモデルを使ったベクトル化関数
def ollama_embed(text):
    r = requests.post(
        "http://localhost:12000/api/embeddings",
        json={"model": "nomic-embed-text", "prompt": text}
    )
    data = r.json()
    return data["embedding"]

この解説

  r = requests.post(
        "http://localhost:12000/api/embeddings",

の部分は local の Ollamaへのリクエスト
これで処理をする

json={"model": "nomic-embed-text", "prompt": text}

の部分は
使用するモデルの指定、今回はnomic-embed-textをベクトル化するモデルとして指定
プロンプトでtext変数を指定

data = r.json()

で返ってきたレスポンスを辞書型へ変換している

return data["embedding"]

で辞書型の中に
Embedding ば埋め込みベクトルなので
これを取り出して返している

もしdataの中を知りたいのなら
Printなどで表示すれば中にembeddingがあるのがわかる

次にwordファイルを読み込む関数

# Wordファイルを読み込む関数
def load_word_document(file):
    return "\n".join(para.text for para in Document(file).paragraphs)

この解説

from docx import Document

で使えることになる doucment を使うことで簡単に処理ができる

Document(file).paragraphs)

によりファイルの段落情報がリストになる

これを for で取り出していく

for para in Document(file).paragraphs

とすれば para に格納していく

そしてその結果を受け取る時に

"\n".join(para.text

としておくことで
Joinで ¥nを指定することで改行されて結合されていく