PyQtとfoliumによる地図表示GUI

この記事ではfoliumで書いた地図をPyQtで表示する方法について説明します。

# はじめに

[folium](https://python-visualization.github.io/folium)はPythonで地図を表示させるためにとても使いやすくて人気なライブラリです。

foliumの仕組みは主にhtmlとJavaScriptコードを生成することです。ただし表示するために普段はjupyterで実行したり、ファイルに保存してブラウザで開けたりするという使い方が多いようですが、その他にも直接GUIを作ってそこで操作しながら表示させることもできます。

PyQtはPythonでGUIを作るのに人気で使い勝手がいいライブラリです。そしてfoliumで描く地図と一緒に使うこともできます。

ということで私は色々試して大体の書き方をこの記事に纏めることにしました。

なお、foliumの基本的な使い方はqiitaにも色んな記事があるので割愛します。私がfoliumの勉強で参考になった記事は全部記事の下の方に載っています。

PyQtに関しては私の以前書いた記事を参考に。

https://qiita.com/phyblas/items/d56003904c83938823f2

今でもPyQt5を使っている人も多いようですが、今回使うのはPyQt6にします。もしPyQt5を使う場合書き方は少しだけ違いますが、その違いについても私の記事を参考にして書き換えたらいいです。


# 準備

まず使うライブリのインストールです。今回の主役はPyQt6とfoliumですが、数字データを手軽に扱うためにnumpyも使います。そしてポリゴンデータが保存されている.shpファイルを読み込むためにpyshp（`import`の時は`shapefile`）というライブラリも必要です。

どれも簡単にpipでインストールできます。
```
pip install pyqt6 folium pyshp numpy
```

ただし環境によって`QWebEngineView`が`ModuleNotFoundError`になる場合もあるらしいです。その場合`QtWebEngineWidgets`もpipでインストールする必要があります。
```
pip install PyQt6-WebEngine
```


# PyQtの中にfoliumを入れる方法

PyQtには`QWebEngineView`というウェジェットがあって、これにhtmlを入れることでウェブページを表示することができます。ということでfoliumで生成したhtmlを`QWebEngineView`に入れたら地図を表示することができます。

まずは簡単な使う例を見てみましょう。
```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget
from PyQt6.QtWebEngineWidgets import QWebEngineView
import folium

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        # 地図のウィジェットを準備
        self.fowg = QWebEngineView(self)
        self.fowg.setGeometry(10,10,600,450) # 位置設定
        # ウィジェットの中で地図を表示させる
        self.chizu_hyouji()
    
    def chizu_hyouji(self):
        # 地図を設定
        foma = folium.Map(
            location=[33.9583,130.9688],
            zoom_start=15
        )
        
        # 指定した場所にマーカーを入れる
        folium.Marker(
            location=[33.9609, 130.9622],
            popup=folium.Popup('和布刈神社',show=True),
            icon=folium.Icon(color='darkred')
        ).add_to(foma)
        
        # htmlを生成する
        html = foma.get_root().render()
        
        # foliumによって生成されたhtmlを入れる
        self.fowg.setHtml(html)

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

実行したらこのように九州最北端の辺りの地図と[和布刈神社](https://ja.wikipedia.org/wiki/和布刈神社)を指すマーカーが現れますね。因みに中心は山頂にある[門司城跡](https://ja.wikipedia.org/wiki/門司城)に設定しています。あそこには石碑くらいしか残っていないのですが、[関門海峡](https://ja.wikipedia.org/wiki/関門海峡)を見渡せる景色はいいですね。

![qpf01.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a67b661f-001d-7653-2211-8e8322166c22.jpeg)

尚、わざわざ地図を描く部分のコードだけ個別のメソッドにしたのはわかりやすくするためだけでなく、更新がある時にこの関数の部分だけもう一度実行することになるからです。詳しくは次に説明します。


# GUIの設定値によって書き換えていく地図

わざわざGUIにするというのは、やはりクリックしたりドラッグしたりすることによって地図を操作したいですね。ではその例を見てみましょう。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QHBoxLayout,QSlider
from PyQt6.QtWebEngineWidgets import QWebEngineView
import folium

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        self.hankei = 500
        hbl = QHBoxLayout()
        self.setLayout(hbl)
        
        # 地図のウィジェット
        self.fowg = QWebEngineView(self)
        hbl.addWidget(self.fowg)
        self.fowg.setFixedSize(480,400)
        
        # スライダーのウィジェット
        self.qslider = QSlider()
        hbl.addWidget(self.qslider)
        self.qslider.setRange(10,1000)
        self.qslider.valueChanged.connect(self.henka)
        self.qslider.setValue(self.hankei)
    
    # スライダーの値が変わったら起動する関数
    def henka(self,x):
        self.hankei = x # 新しい値
        self.chizu_hyouji() # 地図を更新する
    
    def chizu_hyouji(self):
        latlng = [33.5898,130.4207]
        # 地図を設定
        foma = folium.Map(
            location=latlng,
            zoom_start=15
        )
        
        tip = f'博多駅から半径{self.hankei}メートル以内'
        tooltip = folium.Tooltip(tip,style='font-size: 15px;',permanent=True)
        # マーカーを入れる
        folium.Circle(
            location=latlng,
            radius=self.hankei, # スライダーの値から半径
            tooltip=tooltip,
            color='#332299',
            fill_color='#33BB77'
        ).add_to(foma)
        
        # foliumによって生成されたhtmlを入れる
        self.fowg.setHtml(foma.get_root().render())

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

このように、博多駅周辺を表す地図ができて、右側のスライダーを調整することで円の半径が変わります。このような方法を使ってある場所からの距離を見積もることができますね。

![qpf02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f1e7477f-0ccf-c347-36a0-1eba5978eeac.jpeg)



# geojsonを読み込んで表示する

国や都道府県を市区町村の境界線を示すポリゴンデータは**geojson**というjsonの形で保存することが多いです。

それを読み込むには[geojson](https://ja.wikipedia.org/wiki/GeoJSON)というライブラリがありますが、これを使わなくても不通に標準の[json](https://docs.python.org/ja/3.14/library/json.html)ライブラリで読み込むことができます。

今回はここの市区町村データを使います。

https://github.com/niiyz/JapanCityGeoJson/tree/master

これをダウンロードして`geojson/40`フォルダの中の`40203.json`を読み込んで福岡県[久留米市](https://ja.wikipedia.org/wiki/久留米市)の境界線を表示してみます。

```python
import sys,json
from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QVBoxLayout
from PyQt6.QtWebEngineWidgets import QWebEngineView
import folium
import numpy as np

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        # geojsonのデータを読み込む
        self.jsondata = json.load(open('JapanCityGeoJson-master/geojson/40/40203.json'))
        
        vbl = QVBoxLayout()
        self.setLayout(vbl)
        
        # 属性の値を表示
        vbl.addWidget(QLabel(' '.join(self.jsondata['features'][0]['properties'].values())))
        self.setStyleSheet('font-family: Kaiti SC; font-size: 18px; text-align: center')
        
        self.fowg = QWebEngineView(self)
        vbl.addWidget(self.fowg)
        self.fowg.setFixedSize(600,450)
        self.chizu_hyouji()
    
    def chizu_hyouji(self):
        # ポリゴンデータを取得
        pt = np.array(self.jsondata['features'][0]['geometry']['coordinates'][0][0])[:,::-1]
        # 中心とズームのレベルを決めるために極点を求める
        ptmin = pt.min(0)
        ptmax = pt.max(0)
        
        foma = folium.Map(
            location=(ptmin+ptmax)/2,
            zoom_start=9-np.log2((ptmax-ptmin).max())
        )
        
        folium.Polygon(
            locations=pt,
            color='blue',
            fill_color='violet',
            fill_opacity=0.3,
            weight=2,
        ).add_to(foma)
        
        self.fowg.setHtml(foma.get_root().render())

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

![qpf03.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/765b8b9e-1272-e78f-dcb7-3198cc60b736.jpeg)

尚、保存されている座標は「経度、緯度」の順ですが、foliumの入力は「緯度、経度」なので、逆にする必要があります。そこでnumpyを使うと便利です。


# .shpのシェープデータを読み込んで表示する

[地理情報システム](https://ja.wikipedia.org/wiki/地理情報システム)（GIS）のデータは.shpファイルの形で保存されることが多いです。geojsonと違って.shpファイルはバイナリファイルなので、直接読み込むことはできません。[QGIS](https://ja.wikipedia.org/wiki/QGIS)や[ArcGIS](https://ja.wikipedia.org/wiki/ArcGIS)などのGISソフトでは簡単に開けて使えますが、Pythonでも読み込めるライブラリが色々あります。ここではpyshpを使います。

今回はe-Statのデータを使います。ここで「40000 福岡県全域」をダウンロードします。

https://www.e-stat.go.jp/gis/statmap-search?page=1&type=2&aggregateUnitForBoundary=A&toukeiCode=00200521&toukeiYear=2015&serveyId=A002005212015&prefCode=40&coordsys=1&format=shape&datum=2000

その中は.shpを含め色んなファイルがありますが、直接読み込むのは.shpだけで、他は.shpを読み込む時一緒に使われます。

このデータを使って福岡市各区の町丁・字の境界線を表示するGUIを作ってみます。
```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QButtonGroup,QRadioButton,QVBoxLayout,QHBoxLayout
from PyQt6.QtWebEngineWidgets import QWebEngineView
import folium
import shapefile
import numpy as np

lis_ku = ['東区','博多区','中央区','南区','西区','城南区','早良区']

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('福岡市の町丁・字')
        self.data_junbi()
        
        vbl = QVBoxLayout()
        self.setLayout(vbl)
        
        hbl = QHBoxLayout()
        vbl.addLayout(hbl)
        
        # 区を選択するラジオボタン
        self.sentakushi = QButtonGroup()
        for ku in lis_ku:
            rbtn = QRadioButton(ku)
            hbl.addWidget(rbtn)
            self.sentakushi.addButton(rbtn)
        
        # 地図のウェジェット
        self.fowg = QWebEngineView(self)
        vbl.addWidget(self.fowg)
        self.fowg.setFixedSize(500,500)
        
        self.sentakushi.buttonToggled.connect(self.erandara)
        self.sentakushi.buttons()[1].setChecked(True)
    
    # 使うポリゴンデータを準備しておく
    def data_junbi(self):
        # ポリゴンデータを取得
        sf = shapefile.Reader('A002005212015DDSWC40/h27ka40.shp',encoding='cp932')
        self.lis_ptrc = []
        for shp,rc in zip(sf.shapes(),sf.records()):
            if(rc[16]=='福岡市'):
                pt = np.array(shp.points)[:,::-1]
                self.lis_ptrc.append([pt,rc])
    
    # 区を選んだら起動する関数
    def erandara(self,rbtn):
        self.eranda_ku = rbtn.text()
        self.chizu_hyouji()
    
    # 選んだ区の地図を表示する
    def chizu_hyouji(self):
        lis_pt = []
        lis_namae = []
        for pt,rc in self.lis_ptrc:
            if(rc[5]==self.eranda_ku):
                lis_pt.append(pt)
                namae = rc[6]
                if(not namae):
                    namae = None
                lis_namae.append(namae)
        
        pt = np.vstack(lis_pt)
        ptmin = pt.min(0)
        ptmax = pt.max(0)
        foma = folium.Map(
            location=(ptmin+ptmax)/2,
            zoom_start=9-np.log2((ptmax-ptmin).max())
        )
        
        # 一つずつポリゴンを追加していく
        for pt,namae in zip(lis_pt,lis_namae):
            folium.Polygon(
                locations=pt,
                color='darkgreen',
                fill_color='blue',
                fill_opacity=0.3,
                weight=1.5,
                tooltip=namae
            ).add_to(foma)
        
        self.fowg.setHtml(foma.get_root().render())

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

このように博多区の町丁・字の分け方が見えて、マウスを置いたら名前が表示されます。他の区を選んで切り替えられます。

![qpf04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/823ce374-44a7-409c-a0ef-7991579c271b.jpeg)


# 地図のイベントをPyQt側で受け取って使う方法

PyQtの`QWebEngineView`はただfoliumによって作成された地図のウェブページを表示させるので、地図の中に起きた行動は直接PyQtのGUIの方に影響を与えることができません。

それでも調べてみたら、かなり面倒くさいですが方法があります。`QWebChannel`を通じてJavaScriptで起こされたイベントを受け取るという方法です。

コードはかなり複雑でわかりにくくなりますが、仕方ないですね。

例としてポップアップの中にボタンを入れて、そのボタンを押したらGUIの中で場所の名前を表示させるように実装してみます。
```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QLabel
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QObject,pyqtSlot
from PyQt6.QtWebChannel import QWebChannel
import folium

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        
        vbl = QVBoxLayout()
        self.setLayout(vbl)
        
        self.label = QLabel('')
        vbl.addWidget(self.label)
        
        self.fowg = QWebEngineView(self)
        vbl.addWidget(self.fowg)
        self.fowg.setFixedSize(500,400)
        
        # 繋ぐためのチャンネル
        self.channel = QWebChannel()
        self.channel.registerObject('event_obj',EventObj(self))
        self.fowg.page().setWebChannel(self.channel)
        
        self.chizu_hyouji()
    
    def chizu_hyouji(self):
        foma = folium.Map(
            location=(33.589,130.386),
            zoom_start=15
        )
        lis_basho = [
            ('福岡城',(33.5844,130.3832)),
            ('鴻臚館',(33.5860,130.3853)),
            ('赤坂駅',(33.5891,130.3907)),
        ]
        for basho,latlng in lis_basho:
            # ボタンが入っているポップアップ
            popup = folium.Popup(f'<button onclick="btnclick(\'{basho}\')">{basho}</button>',show=True)
            folium.Marker(
                location=latlng,
                popup=popup,
            ).add_to(foma)
            
            html = foma.get_root().render()
            # ボタンのイベントをQt側に送るためのスクリプト
            html += '''
            <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
            <script>
            btnclick = (s)=>{
                new QWebChannel(qt.webChannelTransport, (channel)=>{
                    channel.objects.event_obj.btnclick(s);
                });
            }
            </script>
            '''
        
        self.fowg.setHtml(html)

class EventObj(QObject):
    @pyqtSlot(str)
    def btnclick(self,s):
        self.parent().label.setText(f'「{s}」が選択された')
        

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

![qpf05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/aa7d4f09-a09f-2ed4-6c5b-5e024adeb06c.jpeg)

このようにポップアップの中にJavaScriptを入れて、個別でJavaScript関数の定義も後ろに追加します。更に`QObject`を継承するクラスを定義してその中に実行したいメソッドを書くのです。

もっと簡単な方法があればいいのですが、調べたところこれだけでも精一杯みたいです。

マーカーやポリゴンや直接地図をクリックする時に発動する関数を作ることもできますが、それはかなり遠回しな書き方で更に不気味に見えます。

例えば地図をクリックしたら緯度経度を表示させるように書く例。
```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QLabel,QMessageBox
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QObject,pyqtSlot
from PyQt6.QtWebChannel import QWebChannel
import folium
import re

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        vbl = QVBoxLayout()
        self.setLayout(vbl)
        
        vbl.addWidget(QLabel('地図にクリックしたら緯度経度が表示されます。'))
        self.setStyleSheet('font-family: Kaiti SC; font-size: 19px;')
        
        self.fowg = QWebEngineView(self)
        vbl.addWidget(self.fowg)
        self.fowg.setFixedSize(600,450)
        
        self.channel = QWebChannel()
        self.channel.registerObject('event_obj',EventObj(self))
        self.fowg.page().setWebChannel(self.channel)
        
        self.chizu_hyouji()
    
    def chizu_hyouji(self):        
        foma = folium.Map(
            location=(33.8855,130.8811),
            zoom_start=9
        )
        html = foma.get_root().render()
        pattern = r'((map_\S+) = L\.map\(.+?\))'
        sub = r'''\1
        
        \2.on('click', (e)=>{
            new QWebChannel(qt.webChannelTransport, (channel)=>{
                channel.objects.event_obj.chizuclick(e.latlng.lat,e.latlng.lng);
            });
        })'''
        html = re.sub(pattern,sub,html,flags=re.DOTALL)
        html += '<script src="qrc:///qtwebchannel/qwebchannel.js"></script>'
        self.fowg.setHtml(html)

class EventObj(QObject):
    @pyqtSlot(float,float)
    def chizuclick(self,lat,lng):
        QMessageBox.about(self.parent(),'',f'緯度{lat:.4f}、経度{lng:.4f}')

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()
```

![qpf06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/e3aa2711-2119-dbeb-553d-43bb3c96fd43.jpeg)

ここで正規表現で地図のオブジェクトを検索して、そのオブジェクトに「クリックしたら発動する」イベントを追加することで実現したのです。

# PyQt側から地図を操作する

先程の例と逆にPyQt GUIの方から地図にイベントを送る方法は、`runJavaScript`メソッドを使うことで行えます。

例えばチェックボックスをクリックしたらポップアップが表示されたり閉じられたりするように。
```python
import sys,re
from PyQt6.QtWidgets import QApplication,QWidget,QCheckBox,QVBoxLayout,QHBoxLayout
from PyQt6.QtWebEngineWidgets import QWebEngineView
import folium
import numpy as np

lis_basho = [
    ('中洲川端駅',(33.5950,130.4066)),
    ('呉服町駅',(33.5984,130.4100)),
    ('千代県庁口駅',(33.6025,130.4141)),
    ('馬出九大病院前駅',(33.6089,130.4191)),
    ('箱崎宮前駅',(33.6158,130.4196)),
    ('箱崎九大前駅',(33.6221,130.4210)),
    ('貝塚駅',(33.6322,130.4256))
]

class Qmado(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('福岡市地下鉄箱崎線の駅')
        
        hbl = QHBoxLayout()
        self.setLayout(hbl)
        
        self.fowg = QWebEngineView(self)
        hbl.addWidget(self.fowg)
        self.fowg.setFixedSize(400,500)
        
        self.vbl = QVBoxLayout()
        hbl.addLayout(self.vbl)
        
        lis_latlng = []
        lis_namae = []
        self.lis_cb = []
        for i,(namae,latlng) in enumerate(lis_basho):
            lis_latlng.append(latlng)
            lis_namae.append(namae)
            cb = QCheckBox(namae)
            self.vbl.addWidget(cb)
            self.lis_cb.append(cb)
            cb.toggled.connect(self.ontoggle)
            
        latlng = np.vstack(lis_latlng)
        latlngmin = latlng.min(0)
        latlngmax = latlng.max(0)
        foma = folium.Map(
            location=(latlngmin+latlngmax)/2,
            zoom_start=8-np.log2((latlngmax-latlngmin).max())
        )
        
        for latlng,namae in zip(lis_latlng,lis_namae):
            popup = folium.Popup(namae,close_on_click=False,auto_close=False)
            folium.Marker(
                location=latlng,
                popup=popup
            ).add_to(foma)
        
        html = foma.get_root().render()
        # 生成されたhtmlからマーカーのオブジェクトの変数を収めておく
        self.lis_mkobj = re.findall(r'var (marker_\S+)',html)
        
        self.fowg.setHtml(html)
    
    def ontoggle(self):
        cb = self.sender() # チェックボックスのオブジェクトを取得
        # マーカーのオブジェクトを取得
        mkobj = self.lis_mkobj[self.lis_cb.index(cb)]
        if(cb.isChecked()): # チェックが入っていたらポップアップを表示する
            self.fowg.page().runJavaScript(f'{mkobj}.openPopup()')
        else: # チェックが入っていない場合ポップアップを閉じる
            self.fowg.page().runJavaScript(f'{mkobj}.closePopup()')

qAp = QApplication(sys.argv)
qmado = Qmado()
qmado.show()
qAp.exec()

```

このように駅の場所を指すマーカーができて、右側のチェックボックスにチェックを入れたら駅名のポップアップが現れて、逆にチェックを外したらポップアップが消えます。

![qpf07.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/00b7e536-e754-658c-9e14-0e02f7d8c357.jpeg)


# 終わりに

こんな感じでPyQtとfoliumで地図を弄って色んな処理をするGUIを作ることができます。ただし地図とGUIの間のイベントのやり取りはちょっと書きにくいので、複雑なことをやりたい場合は限界を感じます。

そもそもfoliumの中身はただleafletというJavaScriptライブラリです。foliumでleafletのコードは自動的に生成されますが、全部のleafletの機能が使えるわけではありません。本格的に使いたい場合はやはりfoliumを使うよりも直接leafletで書いた方がいいかもしれません。

それでも簡単な操作だけならfoliumを使うと手軽にPythonで書けるから便利です。

---

*2025年1月25日追記*：もっと使用例としてこの記事を書きました。

https://qiita.com/phyblas/items/3e410b14f21eeb0080d4

# 参考
### folium
- [最寄駅が遠い地域を探す](https://qiita.com/Mya-Mya/items/c00d8d0e9cf2a4401d8d)
- [foliumの基本的な使い方とオープンデータ活用](https://qiita.com/Kumanuron-1910/items/12ce7aa02922927de2f4)
- [folium（位置情報の可視化）の使い方メモ](https://qiita.com/hotoku/items/0d9ddbd24568c1a07f9f)
- [foliumでコロプレス図を描いてみるPart2](https://qiita.com/Mira_332/items/aa8e2a052d3832f3dfc0)
- [folium勉強①](https://qiita.com/kiryu-3/items/0e60a78bc6486a64b5da)
- [イタリア旅行の地図アルバムをPythonで作って共有してみた結果](https://qiita.com/piyo7/items/4e13622761c7b47e4d13)
- [foliumを使って東京23区ラーメンスコアを可視化する](https://qiita.com/xkent/items/d068fc504c8cef217ca8)
- [＃1　Pythonで簡単にマップ制作 ～Folium～](https://zenn.dev/picaneru/articles/c69ebe09d1efdf)
- [python folium を使い、都道府県の夫婦年齢差をプロットする](https://www.monotalk.xyz/blog/python-folinum-を使い都道府県の夫婦年齢差をプロットする/)
- [小学校の在籍人数を地図上にマッピングする（Folium、GeoPandas利用）](https://qiita.com/Gyutan/items/f9b365d88a1ff89671f0)
- [Python で GIS データハンドリング](https://qiita.com/mamurata0924/items/23ab860f4d7429207fac)

### leaflet

- [Leafletで様々なマーカーを表示させる](https://qiita.com/hatsukaze/items/3d708b3323d611dc5725)
- [【Javascript】Leafletでポップアップを全て開く or 全て閉じる](https://qiita.com/takuya_prog/items/6dd21106b613378c0441)

### pyshp
- [GIS に欠かせない Shapefile を深堀ってみる](https://qiita.com/haystacker/items/f045470d5ec5877b9da6)
- [PythonでのShapefile（.shp）操作まとめ](https://qiita.com/c60evaporator/items/78b4148bac6afa4844f9)
- [pythonを用いたshapefileやgeojsonの読込および描画](https://qiita.com/HidKamiya/items/5e7240f8f66c9af8b10e)
- [Maya + pyshp でシェープファイル (.shp)を取り込む](https://qiita.com/Takeharu_Yabe/items/61c5abe3ef158cf9eb07)
- [東海道新幹線のルートマップを描く](https://qiita.com/hamanasu/items/ac91a09de7962f516598)

### PyQt
- [PyQt5入門(1)](https://qiita.com/MamoruItoi/items/340efff6c079a00b04fc)
- [QWebViewの通信を覗き見る](https://qiita.com/maueki/items/e0a56542087569f5f545)
- [GUIとしてのPyQt5の使い方](https://qiita.com/montblanc18/items/0188ff680acf028d4b63)
- [QT Designer + PyQt + 地理地圖](https://ntpuccw.blog/qt-designer-pyqt-地理地圖/)
- [QtWebEngineのWebページからホスト側の処理を呼び出す](https://qiita.com/takumi_akashiro/items/d0c1f586067cb77beef1)
- [PythonとPyQt5で簡易ブラウザを作ってみた話](https://qiita.com/montblanc18/items/88d0b639de86b7cac613)
- [PySide2でWebブラウザをウィジェットとして使う](https://qiita.com/takumi_akashiro/items/b3e0825649e78eb2195f)