たぶん動く...

多分GIS系の人。あくまで個人的見解であり、所属団体を代表するものではありません。

pythonでPDFファイルの表を抽出する

最近、航空管制の公開データを収集していたのですが、日本(航空局)が公開しているデータにはGISデータがほぼないことが分かりました。 FAAだとたくさん あるのに... AIS JAPANからAIViewからウェブブラウザでATSルートとか表示できるけどダウンロードすることができない... ということで、今回は航空局がAIS JAPANで公開しているAIPのPDFファイルをpythonで解析して、位置通報点を抽出し、geojsonで取得してみました。

ソースコード

今回はpandasとtabulaを主に利用します。 工夫したところは、改訂線がある前後の行で抽出がうまくいかなかったので、pdfを読み込ませる際に読み取り範囲を指定したところです。 偶数ページ、奇数ページで余白の設定が違ったので、抽出を分けています。 抽出が失敗した箇所をターミナルに表示できるようにし、後で出力ファイルを手入力できるようにしました。

import pandas as pd
import tabula
import json

import sys

class AIS_Coordinates():
    def __init__(self,string):
        self.coordinates_str = string
        try:
            self.lat_string,self.lon_string=self.coordinates_str.replace('\r',',').split(',')
        except:
            print('err in coordinates')
            pass

    def lat(self):
        return (float(self.lat_string[0:2]) + float(self.lat_string[2:4])/60 + float(self.lat_string[4:-1])/3600)

    def lon(self):
        return (float(self.lon_string[0:3]) + float(self.lon_string[3:5])/60 + float(self.lon_string[5:-1])/3600)

class Name_code():
    def __init__(self,string):
        self.name_str = string
        try:
            replace_str=string.replace('\r',',').replace(' ','')
        except:
#            print('err in init1' + str(self.name_str))
            pass

        try:
            self.name_en,self.name_jp=replace_str.split(',')
        except:
            if(str(self.name_str) !='nan'):
                print('err in  '+str(self.name_str.replace('\r',',')))
            pass

    def name(self):
        try:
            return({"Name-code designator":{"en": self.name_en, "ja":self.name_jp}})
        except:
            return({"Name-code designator":{"en": "None", "ja":"None"}})

if __name__ == '__main__':

    pdf_path='ENR_20220127.pdf'
    out_geojson='waypoint.geojson'

    odd_pages=list(range(519,626,2))
    dfs = tabula.read_pdf(pdf_path,area=[0,70.0,842.00,595.0], lattice=True,pages = odd_pages)

    data = dict()
    data['type'] = 'FeatureCollection'
    data['features'] = []

    for df in dfs:
        column=df.columns[0].replace('\r','')
        if(column=='識別Name-code designator'):
            for i in range (df.index.stop):
                if(str(df.loc[i][1])!='nan'):
                    name_obj=Name_code(df.loc[i][1])
                    name_dict=name_obj.name()

                    if(name_dict['Name-code designator']['en'] != 'None'):
                        coordinates_obj=AIS_Coordinates(df.loc[i][2])
                        data['features'].append({'type': 'Feature',
                              'properties': {
                                    'Name-code designator':
                                    {'en':name_dict["Name-code designator"]["en"], 'ja':name_dict["Name-code designator"]["ja"] }
                                  },
                              'geometry': {'type': 'Point','coordinates': [coordinates_obj.lon(),coordinates_obj.lat()]}
                              })

    even_pages=list(range(520,626,2))
    dfs = tabula.read_pdf(pdf_path,area=[0,27.5,842.00,595.0], lattice=True,pages = even_pages)

    for df in dfs:
        column=df.columns[0].replace('\r','')
        if(column=='識別Name-code designator'):
            for i in range (df.index.stop):
                if(str(df.loc[i][1])!='nan'):
                    name_obj=Name_code(df.loc[i][1])
                    name_dict=name_obj.name()

                    if(name_dict['Name-code designator']['en'] != 'None'):
                        coordinates_obj=AIS_Coordinates(df.loc[i][2])
                        data['features'].append({'type': 'Feature',
                              'properties': {
                                    'Name-code designator':
                                    {'en':name_dict["Name-code designator"]["en"], 'ja':name_dict["Name-code designator"]["ja"] }
                                  },
                              'geometry': {'type': 'Point','coordinates': [coordinates_obj.lon(),coordinates_obj.lat()]}
                              })

    # 辞書オブジェクトをJSONファイルへ出力
    with open(out_geojson, mode='wt', encoding='utf-8') as file:
      json.dump(data, file, ensure_ascii=False, indent=2)

出力した結果、HACHIJOSHIMA(ハチジョウシマ)とMINAMDAITO(ミナミダイトウ)が抽出できませんでした。 名称が途中で改行されると、うまく抽出できないようです。

抽出結果

座標と名前だけ抽出しました。

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "Name-code_designator": {
          "en": "ABASA",
          "ja": "アバサ"
        }
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          123.25232222222222,
          24.439172222222222
        ]
      }
    },
---以下省略---

QGISでプロットするとこんな感じです。

f:id:TTY6335:20220123172419p:plain
report point
オレンジの線はFAA管轄のATSルートです。 ちゃんと抽出した点と線が重なるので抽出した座標は間違ってなさそう...
leafletで表示しようとしましたが、点の数が多すぎて表示できませんでした。今度はweb表示できるようにしてみたいですね。

今回抽出して作成したGeoJSONファイルは以下から入手できます。

GitHub - TTY6335/ats_radio_nav_aids: 航空路誌(AIP)からATSルートや無線航法施設を抽出しました。

参考文献

tabula-py.readthedocs.io qiita.com