Présentation de la projection personnalisée avec Matplotlib

Beginner

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

Introduction

Dans ce laboratoire, nous allons apprendre à créer une projection personnalisée à l'aide de Matplotlib. Nous allons présenter la projection Hammer en mettant en évidence de nombreuses fonctionnalités de Matplotlib. Nous utiliserons Python comme langage de programmation.

Conseils sur la machine virtuelle

Une fois le démarrage de la machine virtuelle terminé, cliquez dans le coin supérieur gauche pour basculer vers l'onglet Carnet de notes pour accéder au carnet Jupyter Notebook pour pratiquer.

Parfois, vous devrez peut-être attendre quelques secondes pour que le carnet Jupyter Notebook ait fini de charger. La validation des opérations ne peut pas être automatisée en raison des limitations du carnet Jupyter Notebook.

Si vous rencontrez des problèmes pendant l'apprentissage, n'hésitez pas à demander à Labby. Donnez votre feedback après la session, et nous résoudrons rapidement le problème pour vous.

Importation des bibliothèques

Tout d'abord, nous allons importer les bibliothèques nécessaires pour créer une projection personnalisée.

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

Création de la classe GeoAxes

Nous allons créer une classe de base abstraite pour les projections géographiques appelée GeoAxes.

class GeoAxes(Axes):
    """
    Une classe de base abstraite pour les projections géographiques
    """

    class ThetaFormatter(Formatter):
        """
        Utilisée pour formater les étiquettes d'échelonnage en theta. Convertit
        l'unité native en radians en degrés et ajoute un symbole de degré.
        """
        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)
        ## Ne pas enregistrer xaxis ou yaxis avec les épines - comme dans
        ## Axes._init_axis() - jusqu'à ce que GeoAxes.xaxis.clear() fonctionne.
        ## self.spines['geo'].register_axis(self.yaxis)

    def clear(self):
        ## docstring héritée
        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)
        ## Pourquoi devons-nous activer les étiquettes d'échelonnage de l'axe y, mais
        ## les étiquettes d'échelonnage de l'axe x sont déjà activées?

        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)

Création de la classe HammerAxes

Nous allons créer une classe personnalisée pour la projection Aitoff-Hammer, une projection cartographique à aire égale appelée HammerAxes.

class HammerAxes(GeoAxes):
    """
    Une classe personnalisée pour la projection Aitoff-Hammer, une projection cartographique
    à aire égale.

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

    ## La projection doit spécifier un nom. Celui-ci sera utilisé par
    ## l'utilisateur pour sélectionner la projection,
    ## c'est-à-dire ``subplot(projection='custom_hammer')``.
    name = 'custom_hammer'

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

        def __init__(self, resolution):
            """
            Crée une nouvelle transformation de Hammer. La résolution est le nombre d'étapes
            pour interpoler entre chaque segment de ligne d'entrée pour approximer son
            trajet dans l'espace courbe de Hammer.
            """
            Transform.__init__(self)
            self._resolution = resolution

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

            ## Pré-calculer quelques valeurs
            demi_longitude = longitude / 2
            cos_latitude = np.cos(latitude)
            racine_deux = np.sqrt(2)

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

        def transform_path_non_affine(self, path):
            ## vertices = path.vertices
            ipath = path.interpolé(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)

Enregistrer la projection

Maintenant, nous allons enregistrer la projection avec Matplotlib afin que l'utilisateur puisse la sélectionner.

register_projection(HammerAxes)

Créer un exemple

Enfin, nous allons créer un exemple utilisant la projection personnalisée.

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

    ## Maintenant, créons un exemple simple utilisant la projection personnalisée.
    fig, ax = plt.subplots(subplot_kw={'projection': 'custom_hammer'})
    ax.plot([-1, 1, 1], [-1, -1, 1], "o-")
    ax.grid()

    plt.show()

Sommaire

Dans ce laboratoire, nous avons appris à créer une projection personnalisée à l'aide de Matplotlib. Nous avons créé une projection personnalisée appelée projection de Hammer en exploitant de nombreuses fonctionnalités de Matplotlib. Nous avons appris à créer la classe GeoAxes, la classe HammerAxes, à enregistrer la projection et à créer un exemple utilisant la projection personnalisée.