GeoPandas のお勉強(ゴミコードのみ)
import geopandas as gpd import pandas as pd import random import svgwrite import re import datetime print( datetime.datetime.now()) #基盤地図情報のgeoJSONデータを読み込み gdf = gpd.read_file('./N03-20230101_12_GML/N03-23_12_230101.geojson', encoding='SHIFT-JIS') #列の名前を変更 gdf_renamed = gdf.rename(columns={"N03_001":"Prefectures", \ "N03_002":"BranchGov", \ "N03_003":"County", \ "N03_004":"City", \ "N03_007":"AreaCode"}) #東京都以外の"City"列の名前に「区」が含まれる行の"City"列の値を"Country"と同じにする gdf_renamed.loc[ (gdf_renamed["Prefectures"]!="東京都") & (gdf_renamed['City'].str.contains("区")) , 'City'] = gdf_renamed["County"] #同じ市町村名を持つ行は一つの行にまとめる gdf_grouped = gdf_renamed.dissolve(by=['Prefectures','City'], as_index=False) #各市町村に適当すぎる色をつける hexColors = [] for i in range(len(gdf_grouped)): hexColor = '#' + format(random.randrange(255), '02x') + \ format(random.randrange(255), '02x') + \ format(random.randrange(255), '02x') hexColors.append(hexColor) cityNames = [] for i , row in gdf_grouped.iterrows(): cityNames.append( row["City"] ) pdf_colors = pd.DataFrame({ 'City':cityNames, 'Color':hexColors}) pdf_mrgedColor = pd.merge(gdf_grouped, pdf_colors ,on='City' ) gdf_withColor = gpd.GeoDataFrame(pdf_mrgedColor) #図法をWebメルカトルに gdf_WebMerc = gdf_withColor.to_crs("EPSG:3857") #ポイント削減 gds_WebMercSimple = gdf_WebMerc["geometry"].simplify(tolerance=10.) #svgファイルへの書き出し docSize = 2000 viewBoxSize = "0 0 %d %d"%(docSize, docSize) bbox = gdf_WebMerc.total_bounds xLength = bbox[2] - bbox[0] yLength = bbox[3] - bbox[1] if xLength > yLength: scale = docSize / xLength else: scale = docSize / yLength gds_WebMercSimple = gds_WebMercSimple.translate( -bbox[0] - xLength*0.5 , -bbox[1] - yLength*0.5) gds_WebMercSimple = gds_WebMercSimple.scale(scale, -scale, origin=(docSize * 0.5 ,docSize * 0.5)) gdf_WebMercDeleted = gdf_WebMerc.drop('geometry', axis=1) gdf_WebMercDeleted['geometry'] = gds_WebMercSimple dwg = svgwrite.Drawing( "chibaMapWebMelc.svg", size=( docSize , docSize ), profile='tiny') grp1 = dwg.add(dwg.g(id='市町村')) for i , row in gdf_WebMercDeleted.iterrows(): city_svg = row.geometry.svg() startIndies = [m.span() for m in re.finditer('M', city_svg)] endIndies = [m.span() for m in re.finditer('z', city_svg)] svgpath = "" for j in range(len(startIndies)): svgpath += city_svg[startIndies[j][0]:endIndies[j][1]] path = dwg.path( svgpath , stroke='none', stroke_width=3.0, fill=row['Color'], id=row['Prefectures'] + row['City']) grp1.add( path ) dwg.save() print( datetime.datetime.now())
GeoPandas のお勉強
必要にかられてというより、ちょっと落ちつているのでGeoPandasを触っているのですけど基本が分かってないから苦戦をしています。
shapefileとかgeoJSONを読み込むとテーブルデータが作成されましてそれがGeoDataFrameというもの。表だから行と列があり、各行にindexといくつかのデータが保持できるのだけど、その中にジオメトリ情報も保持できて、それらデータを使ってまあ便利って処理ができるのがGeoPandasということみたい。ジオメトリってのは点や線、ポリゴンっすね。
国土数値情報ダウンロードサイトからダウンロードできる行政区域データの千葉県のを落としてそれを読んでみました。そのデータを元に千葉県の地図のsvgで書き出すのが目的です。
import geopandas as gpd gdf = gpd.read_file('./N03-20230101_12_GML/N03-23_12_230101.geojson', encoding='SHIFT-JIS') gdf.info()
そうすると帰ってくる内容は
<class 'geopandas.geodataframe.GeoDataFrame'> RangeIndex: 2265 entries, 0 to 2264 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 N03_001 2265 non-null object 1 N03_002 0 non-null object 2 N03_003 86 non-null object 3 N03_004 2265 non-null object 4 N03_007 2265 non-null object 5 geometry 2265 non-null geometry dtypes: geometry(1), object(5) memory usage: 106.3+ KB
というものでした。N03_001〜N03_007が列の名前ということになります。わかりにくいので名前をつけてみるなら、たとえば
N03_001→都道府県名→Prefectures
N03_002→支庁・振興局名→BranchGov
N03_003→郡・政令都市名→County
N03_004→市区町村名→City
N03_005→無い
N03_006→無い
N03_007→行政区域コード→AreaCode
とかにしてみます。なを英語としてはたぶん正しくないですw
gdf_renamed = gdf.rename(columns={"N03_001":"Prefectures", \ "N03_002":"BranchGov", \ "N03_003":"County", \ "N03_004":"City", \ "N03_007":"AreaCode"}) gdf_renamed.info()
<class 'geopandas.geodataframe.GeoDataFrame'> RangeIndex: 2265 entries, 0 to 2264 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Prefectures 2265 non-null object 1 BranchGov 0 non-null object 2 County 86 non-null object 3 City 2265 non-null object 4 AreaCode 2265 non-null object 5 geometry 2265 non-null geometry dtypes: geometry(1), object(5) memory usage: 106.3+ KB
市町村ごとに分割された地図を描きたいので、City列で同じ名前の行の中のジオメトリを一行にまとめるって処理をする時、dissolveメソッドを使うと良いのだけど、政令指定都市の場合Cityに入っているのは〜区なんすよね。千葉市青葉区だったら千葉市がCountyに、青葉区がCityに入っている。この目的とするところではCityに千葉市が入っていて欲しいのでどうしようかと悩みました。とりあえずCountyに値が入っててCityの値に「区」が含まれているという条件の行のCity列にCounty列の値を入れるって処理をすることにしました。 東京都以外の都道府県に市町村に「区」の入った名前はないと信じて、東京都以外のCityのとこに「区」が入った行のCity列にCounty列の値を入れるって処理にします。(ネット上の情報によると自治体名に区が入っているところはないそうなので、これで行けるはず)
gdf_renamed.loc[ (gdf_renamed["Prefectures"]!="東京都") & (gdf_renamed['City'].str.contains("区")) , 'City'] = gdf_renamed["County"]
これで千葉市青葉区みたいに〜区のところは〜市だけになったのでdissolveで1市町村区1行という状態を作ります。
gdf_grouped = gdf_renamed.dissolve(by=['City'], as_index=False)
以上で出来上がるGeoDataFrameであるgdf_groupedを図法変換してplotしてあげればとりあえずいいかと思ったのだけど、plotで使用される matplotlib.pyplot の処理だと、どうやら市町村ごとにsvgの別グループとして設定することができないようなのですね。仮に全ての市町村を別々の色に塗りつぶしていたとしても、〜町の形が欲しいってIllusratorで選ぼうとしても島がいっぱいあるような町だと必要要素を選択するのに難儀しそうです。
ということで、市町村区ごとにsvgの別々のグループにできるようにsvgwriteモジュールを使うことにしました。とはいえ、svgに書き出す前にGeoDataFrameの各市町村区の行にランダムな色を割り振り、その色でその地域の範囲を塗りつぶそうと思います。
hexColors = [] for i in range(len(gdf_grouped)): hexColor = '#' + format(random.randrange(255), '02x') + \ format(random.randrange(255), '02x') + \ format(random.randrange(255), '02x') hexColors.append(hexColor) cityNames = [] for i , row in gdf_grouped.iterrows(): cityNames.append( row["City"] ) pdf_colors = pd.DataFrame({ 'City':cityNames, 'Color':hexColors}) pdf_mrgedColor = pd.merge(gdf_grouped, pdf_colors ,on='City' ) gdf_withColor = gpd.GeoDataFrame(pdf_mrgedColor)
16進数の色指定の文字列を作る方法がそうやるんだって感心してました。gdf_withColorは各行にランダムな色を設定したColor列が追加されてます。
図法変換し、Illustratorはパス1つにつき32000個のポイントしか許さないという制限に対処するためにポイント数を減らす処理をします。
#図法をWebメルカトルに gdf_WebMerc = gdf_withColor.to_crs("EPSG:3857") gds_WebMercSimple = gdf_WebMerc["geometry"].simplify(tolerance=10.)
svgへの書き出しです。svgファイルの中央に図形が配置されるようにするための処理がこれでいいのかちょっとわからんです。
docSize = 2000 viewBoxSize = "0 0 %d %d"%(docSize, docSize) bbox = gdf_WebMerc.total_bounds xLength = bbox[2] - bbox[0] yLength = bbox[3] - bbox[1] if xLength > yLength: scale = docSize / xLength else: scale = docSize / yLength gds_WebMercSimple = gds_WebMercSimple.translate( -bbox[0] - xLength*0.5 , -bbox[1] - yLength*0.5) gds_WebMercSimple = gds_WebMercSimple.scale(scale, -scale, origin=(docSize * 0.5 ,docSize * 0.5)) gdf_WebMercDeleted = gdf_WebMerc.drop('geometry', axis=1) gdf_WebMercDeleted['geometry'] = gds_WebMercSimple dwg = svgwrite.Drawing( "chibaMapWebMelc.svg", size=( docSize , docSize ), profile='tiny') grp1 = dwg.add(dwg.g(id='市町村')) for i , row in gdf_WebMercDeleted.iterrows(): city_svg = row.geometry.svg() startIndies = [m.span() for m in re.finditer('M', city_svg)] endIndies = [m.span() for m in re.finditer('z', city_svg)] svgpath = "" for j in range(len(startIndies)): svgpath += city_svg[startIndies[j][0]:endIndies[j][1]] path = dwg.path( svgpath , stroke='none', stroke_width=3.0, fill=row['Color'], id=row['Prefectures'] + row['City']) grp1.add( path ) dwg.save()