Exhibición de proyección personalizada de Matplotlib

PythonPythonBeginner
Practicar Ahora

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

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderemos cómo crear una proyección personalizada utilizando Matplotlib. Presentaremos la proyección Hammer al resaltar muchas de las características de Matplotlib. Utilizaremos Python como nuestro lenguaje de programación.

Consejos sobre la VM

Una vez que se haya iniciado la VM, haga clic en la esquina superior izquierda para cambiar a la pestaña Cuaderno y acceder a Jupyter Notebook para practicar.

A veces, es posible que tenga que esperar unos segundos a que Jupyter Notebook termine de cargarse. La validación de las operaciones no se puede automatizar debido a las limitaciones de Jupyter Notebook.

Si tiene problemas durante el aprendizaje, no dude en preguntar a Labby. Deje sus comentarios después de la sesión y lo resolveremos rápidamente para usted.

Importar bibliotecas

Primero, importaremos las bibliotecas necesarias para crear una proyección 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

Crear la clase GeoAxes

Crearemos una clase base abstracta para proyecciones geográficas llamada GeoAxes.

class GeoAxes(Axes):
    """
    Una clase base abstracta para proyecciones geográficas
    """

    class ThetaFormatter(Formatter):
        """
        Se utiliza para formatear las etiquetas de los ticks de theta. Convierte la unidad nativa de radianes a grados y agrega un símbolo de grado.
        """
        def __init__(self, round_to=1.0):
            self._round_to = round_to

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

    RESOLUTION = 75

    def _init_axis(self):
        self.xaxis = maxis.XAxis(self)
        self.yaxis = maxis.YAxis(self)
        ## No registre xaxis o yaxis con spines -- como se hace en
        ## Axes._init_axis() -- hasta que GeoAxes.xaxis.clear() funcione.
        ## self.spines['geo'].register_axis(self.yaxis)

    def clear(self):
        ## docstring heredado
        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 qué necesitamos encender las etiquetas de los ticks del eje y, pero
        ## las etiquetas de los ticks del eje x ya están encendidas?

        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)

Crear la clase HammerAxes

Crearemos una clase personalizada para la proyección Aitoff-Hammer, una proyección de mapa de área igual llamada HammerAxes.

class HammerAxes(GeoAxes):
    """
    Una clase personalizada para la proyección Aitoff-Hammer, una proyección de mapa de área igual.

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

    ## La proyección debe especificar un nombre. Esto se utilizará por el
    ## usuario para seleccionar la proyección,
    ## es decir, ``subplot(projection='custom_hammer')``.
    name = 'custom_hammer'

    class HammerTransform(Transform):
        """La transformada base de Hammer."""
        input_dims = output_dims = 2

        def __init__(self, resolution):
            """
            Crea una nueva transformada de Hammer. La resolución es el número de pasos
            para interpolar entre cada segmento de línea de entrada para aproximar su
            trayectoria en el espacio curvo de Hammer.
            """
            Transform.__init__(self)
            self._resolution = resolution

        def transform_non_affine(self, ll):
            longitud, latitud = ll.T

            ## Pre-calculamos algunos valores
            mitad_longitud = longitud / 2
            coseno_latitud = np.cos(latitud)
            raiz2 = np.sqrt(2)

            alfa = np.sqrt(1 + coseno_latitud * np.cos(mitad_longitud))
            x = (2 * raiz2) * (coseno_latitud * np.sin(mitad_longitud)) / alfa
            y = (raiz2 * np.sin(latitud)) / alfa
            return np.column_stack([x, y])

        def transform_path_non_affine(self, path):
            ## vertices = path.vertices
            ipath = path.interpolado(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)
            longitud = 2 * np.arctan((z * x) / (2 * (2 * z ** 2 - 1)))
            latitud = np.arcsin(y*z)
            return np.column_stack([longitud, latitud])

        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 la proyección

Ahora registraremos la proyección con Matplotlib para que el usuario pueda seleccionarla.

register_projection(HammerAxes)

Crear un ejemplo

Finalmente, crearemos un ejemplo utilizando la proyección personalizada.

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

    ## Ahora hagamos un ejemplo simple utilizando la proyección personalizada.
    fig, ax = plt.subplots(subplot_kw={'projection': 'custom_hammer'})
    ax.plot([-1, 1, 1], [-1, -1, 1], "o-")
    ax.grid()

    plt.show()

Resumen

En este laboratorio, aprendimos cómo crear una proyección personalizada utilizando Matplotlib. Creamos una proyección personalizada llamada proyección de Hammer al simplificar muchas características de Matplotlib. Aprendimos cómo crear la clase GeoAxes, la clase HammerAxes, registrar la proyección y crear un ejemplo utilizando la proyección personalizada.