Matplotlib 사용자 정의 투영법 데모

Beginner

This tutorial is from open-source community. Access the source code

소개

이 랩에서는 Matplotlib 을 사용하여 사용자 정의 투영법을 만드는 방법을 배웁니다. Matplotlib 의 많은 기능을 활용하여 해머 투영법 (Hammer projection) 을 시연할 것입니다. 프로그래밍 언어로는 Python 을 사용합니다.

VM 팁

VM 시작이 완료되면, 왼쪽 상단 모서리를 클릭하여 Notebook 탭으로 전환하여 실습을 위해 Jupyter Notebook에 접속하십시오.

때로는 Jupyter Notebook 이 로딩을 완료하는 데 몇 초 정도 기다려야 할 수 있습니다. Jupyter Notebook 의 제한으로 인해 작업의 유효성 검사는 자동화될 수 없습니다.

학습 중에 문제가 발생하면 언제든지 Labby 에게 문의하십시오. 세션 후 피드백을 제공해주시면 문제를 신속하게 해결해 드리겠습니다.

라이브러리 임포트

먼저, 사용자 정의 투영법을 생성하는 데 필요한 라이브러리를 임포트합니다.

import numpy as np
import matplotlib
from matplotlib.axes import Axes
import matplotlib.axis as maxis
from matplotlib.patches import Circle
from matplotlib.path import Path
from matplotlib.projections import register_projection
import matplotlib.spines as mspines
from matplotlib.ticker import FixedLocator, Formatter, NullLocator
from matplotlib.transforms import Affine2D, BboxTransformTo, Transform

GeoAxes 클래스 생성

GeoAxes라는 지리 투영법 (geographic projections) 을 위한 추상 기본 클래스를 생성합니다.

class GeoAxes(Axes):
    """
    지리 투영법을 위한 추상 기본 클래스
    """

    class ThetaFormatter(Formatter):
        """
        세타 눈금 레이블의 형식을 지정하는 데 사용됩니다. 네이티브
        단위인 라디안을 도 (degree) 로 변환하고 도 기호를 추가합니다.
        """
        def __init__(self, round_to=1.0):
            self._round_to = round_to

        def __call__(self, x, pos=None):
            degrees = round(np.rad2deg(x) / self._round_to) * self._round_to
            return f"{degrees:0.0f}\N{DEGREE SIGN}"

    RESOLUTION = 75

    def _init_axis(self):
        self.xaxis = maxis.XAxis(self)
        self.yaxis = maxis.YAxis(self)
        ## GeoAxes.xaxis.clear() 가 작동할 때까지,
        ## Axes._init_axis() 에서 수행되는 것처럼, xaxis 또는 yaxis 를 스파인 (spines) 에 등록하지 마십시오.
        ## self.spines['geo'].register_axis(self.yaxis)

    def clear(self):
        ## docstring 상속
        super().clear()

        self.set_longitude_grid(30)
        self.set_latitude_grid(15)
        self.set_longitude_grid_ends(75)
        self.xaxis.set_minor_locator(NullLocator())
        self.yaxis.set_minor_locator(NullLocator())
        self.xaxis.set_ticks_position('none')
        self.yaxis.set_ticks_position('none')
        self.yaxis.set_tick_params(label1On=True)
        ## 왜 y 축 눈금 레이블을 켜야 하지만,
        ## x 축 눈금 레이블은 이미 켜져 있습니까?

        self.grid(rcParams['axes.grid'])

        Axes.set_xlim(self, -np.pi, np.pi)
        Axes.set_ylim(self, -np.pi / 2.0, np.pi / 2.0)

HammerAxes 클래스 생성

HammerAxes라고 불리는 등면적 지도 투영법인 Aitoff-Hammer 투영법을 위한 사용자 정의 클래스를 생성합니다.

class HammerAxes(GeoAxes):
    """
    Aitoff-Hammer 투영법, 등면적 지도 투영법을 위한 사용자 정의 클래스.

    https://en.wikipedia.org/wiki/Hammer_projection
    """

    ## 투영법은 이름을 지정해야 합니다. 이것은
    ## 사용자가 투영법을 선택하는 데 사용됩니다.
    ## 즉, ``subplot(projection='custom_hammer')``.
    name = 'custom_hammer'

    class HammerTransform(Transform):
        """기본 Hammer 변환."""
        input_dims = output_dims = 2

        def __init__(self, resolution):
            """
            새로운 Hammer 변환을 생성합니다. 해상도는 각 입력 선분 사이를 보간하여
            곡선 Hammer 공간에서 경로를 근사화하는 단계 수입니다.
            """
            Transform.__init__(self)
            self._resolution = resolution

        def transform_non_affine(self, ll):
            longitude, latitude = ll.T

            ## 일부 값 미리 계산
            half_long = longitude / 2
            cos_latitude = np.cos(latitude)
            sqrt2 = np.sqrt(2)

            alpha = np.sqrt(1 + cos_latitude * np.cos(half_long))
            x = (2 * sqrt2) * (cos_latitude * np.sin(half_long)) / alpha
            y = (sqrt2 * np.sin(latitude)) / alpha
            return np.column_stack([x, y])

        def transform_path_non_affine(self, path):
            ## vertices = path.vertices
            ipath = path.interpolated(self._resolution)
            return Path(self.transform(ipath.vertices), ipath.codes)

        def inverted(self):
            return HammerAxes.InvertedHammerTransform(self._resolution)

    class InvertedHammerTransform(Transform):
        input_dims = output_dims = 2

        def __init__(self, resolution):
            Transform.__init__(self)
            self._resolution = resolution

        def transform_non_affine(self, xy):
            x, y = xy.T
            z = np.sqrt(1 - (x / 4) ** 2 - (y / 2) ** 2)
            longitude = 2 * np.arctan((z * x) / (2 * (2 * z ** 2 - 1)))
            latitude = np.arcsin(y*z)
            return np.column_stack([longitude, latitude])

        def inverted(self):
            return HammerAxes.HammerTransform(self._resolution)

    def __init__(self, *args, **kwargs):
        self._longitude_cap = np.pi / 2.0
        super().__init__(*args, **kwargs)
        self.set_aspect(0.5, adjustable='box', anchor='C')
        self.clear()

    def _get_core_transform(self, resolution):
        return self.HammerTransform(resolution)

투영법 등록

이제 사용자가 선택할 수 있도록 Matplotlib 에 투영법을 등록합니다.

register_projection(HammerAxes)

예제 생성

마지막으로, 사용자 정의 투영법을 사용하여 예제를 생성합니다.

if __name__ == '__main__':
    import matplotlib.pyplot as plt

    ## 이제 사용자 정의 투영법을 사용하여 간단한 예제를 만듭니다.
    fig, ax = plt.subplots(subplot_kw={'projection': 'custom_hammer'})
    ax.plot([-1, 1, 1], [-1, -1, 1], "o-")
    ax.grid()

    plt.show()

요약

이 랩에서는 Matplotlib 을 사용하여 사용자 정의 투영법을 만드는 방법을 배웠습니다. Matplotlib 의 많은 기능을 활용하여 Hammer 투영법이라는 사용자 정의 투영법을 만들었습니다. GeoAxes 클래스, HammerAxes 클래스를 생성하고, 투영법을 등록하며, 사용자 정의 투영법을 사용하여 예제를 만드는 방법을 배웠습니다.