【PyQt】QtDesigner上にBasemapを埋め込む(格上げを使う)

# はじめに
QtDesignerのGUI上に[Basemap](https://matplotlib.org/basemap/)を埋め込みたい。でも、QtDesignerのウィジェットボックスにはBasemapなんてない。そんな時はウィジェットの**格上げ**をしましょう。

# 環境
+ macOS
+ python=3.6.4
+ PyQt5=5.12.1
+ basemap=1.1.0
+ QtDesigner=5.6.2

# Basemapインストール
まずはBasemapのインストールをします。
[matplotlib.basemapで簡単にマップを描画する](https://qiita.com/msrks/items/ed18a2653bc177a24cca)を参考に。

```bash:@mac
$ xcode-select --install
$ brew install geos
$ brew install wget
$ wget https://github.com/matplotlib/basemap/archive/v1.1.0.tar.gz
$ tar zxvf v1.1.0.tar.gz
$ cd basemap-1.1.0
$ python setup.py install
```

先頭のxcode-select --installは、geosのインストール時にエラーが出たため、[こちら](https://qiita.com/zousaaan3/items/d1d9af1bf55c32ca5fe8)を参考に加えました。

インストールが出来たら、[サンプル](https://matplotlib.org/basemap/users/examples.html)が動くことを確認しましょう。

# UIファイルの作成
QtDesigner上で、Basemapを埋め込む準備をします。QtDesignerの基本的な使い方は以下を参考にして下さい。
[[入門]PyQtでHello Worldを表示する](https://qiita.com/grinpeaceman/items/54b439bfa52640c444e1)

QtDesignerで新規ファイルを作成し、ウィジェットボックスから「Widget」を選択し、ドラッグ&ドロップで画面に配置します。

<img width="802" alt="スクリーンショット 2019-05-25 17.00.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/9bd589d1-c7a3-1178-7947-baa0c03025c7.png">

このQWidgetの名前は「worldMap」としておきます。ここにBasemapを埋め込むことになります。

<img width="376" alt="スクリーンショット 2019-05-25 17.00.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/a78c553e-6946-1a9f-f4da-3197ef964b10.png">

ここで一旦ファイルを保存しておきましょう。名前は「world_map.ui」とします。
保存したら、作成した.uiファイルを.pyファイルに変換しましょう。

`$ pyuic5 world_map.ui > world_map_ui.py`

# Basemapの実体を作成
Basemapの実体をもつファイルを作成します。名前は「basemap_sample.py」とし、内容は以下のようにしました。

```python:basemap_sample.py
import sys

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from mpl_toolkits.basemap import Basemap
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QWidget

from world_map_ui import Ui_MainWindow


class BasemapSample(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(BasemapSample, self).__init__(parent)
        self.setupUi(self)


class WorldMap(QWidget):
    def __init__(self, parent):
        super(WorldMap, self).__init__(parent)
        self._fig = Figure(figsize=(4.0, 3.0), dpi=100)
        axes = self._fig.add_axes([0.0, 0.0, 1.0, 1.0])
        self._canvas = FigureCanvas(self._fig)
        self._canvas.setParent(parent)
        map = Basemap(projection='cyl', resolution='c', ellps="WGS84", ax=axes)
        map.drawcoastlines()
        map.drawmapboundary(fill_color='#bce2e8')
        map.fillcontinents(color='#80aba9',lake_color='#bce2e8')
        self._canvas.draw()

    # サイズ調整
    def setGeometry(self, rect):
        super().setGeometry(rect)
        self._canvas.setGeometry(rect)
        dpi = self._fig.get_dpi()
        self._fig.set_size_inches(rect.width() / dpi,
                                  rect.height() / dpi)


if __name__ == '__main__':
    argvs = sys.argv
    app = QApplication(argvs)
    basemap_sample = BasemapSample()
    basemap_sample.show()
    sys.exit(app.exec_())

```

クラスが二つありますが、BasemapSampleクラスは先ほど作成したUIファイルを継承し表示するもの、WorldMapクラスはUIファイルで作成したworldMap(QWidget)の格上げ先クラスになります。

# 格上げをする
QtDesignerに戻ります。配置したQWidgetの上で右クリックするとメニューが表示されます。その中の「格上げ先を指定...」を選択します。

<img width="802" alt="スクリーンショット 2019-05-25 17.04.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/efe1ea9a-5532-47a8-8465-07838e764d38.png">

「格上げされたウィジェット」という画面が出てきます。下部の新しい格上げされたクラスのところに、格上げされたクラス名、ヘッダファイルを入力するフィールドがあるので、以下のようにインプットします。

<img width="540" alt="スクリーンショット 2019-05-25 18.14.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/e7d22c2c-3830-e5e8-0297-62d555f4343e.png">

「追加」ボタンを押し、「格上げ」をして画面を閉じます。
すると、オブジェクトインスペクタ上の、worldMapのクラスがQWidgetから、格上げしたWorldMapになっていることがわかります。

<img width="375" alt="スクリーンショット 2019-05-25 18.19.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/194e780e-9ca3-d410-c431-c642f05cd724.png">

ここまで出来たら、ファイルを保存し、改めて.ui > .py 変換をします。

`$ pyuic5 world_map.ui > world_map_ui.py`

```python:world_map_ui.py
from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.worldMap = WorldMap(self.centralwidget)
        self.worldMap.setGeometry(QtCore.QRect(60, 40, 680, 440))
        self.worldMap.setObjectName("worldMap")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))


from basemap_sample import WorldMap

```

# 実行してみる
ファイルを実行してみます。

<img width="800" alt="スクリーンショット 2019-05-25 18.23.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/404447/577e6d08-2886-eae9-3817-ac010a0e8c78.png">

配置したQWidgetの部分に無事、Basemapが埋め込まれました。

自分が配置したいGUIがQtDesigner上にない場合は、自分でクラスを作成し、格上げをしてみましょう。