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.