Demonstração de Projeção Personalizada Matplotlib

Beginner

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

Introdução

Neste laboratório, aprenderemos como criar uma projeção personalizada usando Matplotlib. Apresentaremos a projeção Hammer, aproveitando muitos recursos do Matplotlib. Usaremos Python como nossa linguagem de programação.

Dicas para a VM

Após a inicialização da VM, clique no canto superior esquerdo para mudar para a aba Notebook e acessar o Jupyter Notebook para praticar.

Às vezes, pode ser necessário aguardar alguns segundos para que o Jupyter Notebook termine de carregar. A validação das operações não pode ser automatizada devido a limitações no Jupyter Notebook.

Se você enfrentar problemas durante o aprendizado, sinta-se à vontade para perguntar ao Labby. Forneça feedback após a sessão, e resolveremos o problema prontamente para você.

Importar Bibliotecas

Primeiramente, importaremos as bibliotecas necessárias para criar uma projeção personalizada.

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

Criar Classe GeoAxes

Criaremos uma classe base abstrata para projeções geográficas chamada GeoAxes.

class GeoAxes(Axes):
    """
    Uma classe base abstrata para projeções geográficas
    """

    class ThetaFormatter(Formatter):
        """
        Usado para formatar os rótulos de marcação theta. Converte a unidade nativa
        de radianos em graus e adiciona um símbolo de grau.
        """
        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)
        ## Não registrar xaxis ou yaxis com spines -- como feito em
        ## Axes._init_axis() -- até que GeoAxes.xaxis.clear() funcione.
        ## self.spines['geo'].register_axis(self.yaxis)

    def clear(self):
        ## docstring herdado
        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)
        ## Por que precisamos ativar os rótulos de marcação yaxis, mas
        ## os rótulos de marcação xaxis já estão ativados?

        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)

Criar Classe HammerAxes

Criaremos uma classe personalizada para a projeção Aitoff-Hammer, uma projeção de mapa de área igual chamada HammerAxes.

class HammerAxes(GeoAxes):
    """
    Uma classe personalizada para a projeção Aitoff-Hammer, uma projeção de mapa de área igual.

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

    ## A projeção deve especificar um nome. Isso será usado pelo
    ## usuário para selecionar a projeção,
    ## i.e. ``subplot(projection='custom_hammer')``.
    name = 'custom_hammer'

    class HammerTransform(Transform):
        """A transformação Hammer base."""
        input_dims = output_dims = 2

        def __init__(self, resolution):
            """
            Cria uma nova transformação Hammer. Resolução é o número de passos
            para interpolar entre cada segmento de linha de entrada para aproximar seu
            caminho no espaço Hammer curvo.
            """
            Transform.__init__(self)
            self._resolution = resolution

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

            ## Pré-calcular alguns valores
            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)

Registrar Projeção

Agora registraremos a projeção com o Matplotlib para que o usuário possa selecioná-la.

register_projection(HammerAxes)

Criar Exemplo

Finalmente, criaremos um exemplo usando a projeção personalizada.

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

    ## Agora, faça um exemplo simples usando a projeção personalizada.
    fig, ax = plt.subplots(subplot_kw={'projection': 'custom_hammer'})
    ax.plot([-1, 1, 1], [-1, -1, 1], "o-")
    ax.grid()

    plt.show()

Resumo

Neste laboratório, aprendemos como criar uma projeção personalizada usando o Matplotlib. Criamos uma projeção personalizada chamada projeção Hammer, aliviando muitas funcionalidades do Matplotlib. Aprendemos como criar a classe GeoAxes, a classe HammerAxes, registrar a projeção e criar um exemplo usando a projeção personalizada.