Custom Mercator Latitude Scale in Matplotlib

PythonPythonBeginner
Practice Now

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

Introduction

Matplotlib is a powerful data visualization library for Python. It provides a number of built-in scales for plotting data, but sometimes you may need a custom scale for your specific use case. In this lab, we will show you how to create a custom scale that uses the Mercator projection for latitude data.

VM Tips

After the VM startup is done, click the top left corner to switch to the Notebook tab to access Jupyter Notebook for practice.

Sometimes, you may need to wait a few seconds for Jupyter Notebook to finish loading. The validation of operations cannot be automated because of limitations in Jupyter Notebook.

If you face issues during learning, feel free to ask Labby. Provide feedback after the session, and we will promptly resolve the problem for you.

Import necessary libraries

We will start by importing the necessary libraries.

import numpy as np
from numpy import ma

from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import FixedLocator, FuncFormatter

Define the MercatorLatitudeScale class

Next, we will define the MercatorLatitudeScale class that will implement the custom scale. This class will inherit from mscale.ScaleBase.

class MercatorLatitudeScale(mscale.ScaleBase):
    """
    Scales data in range -pi/2 to pi/2 (-90 to 90 degrees) using
    the system used to scale latitudes in a Mercator__ projection.

    The scale function:
      ln(tan(y) + sec(y))

    The inverse scale function:
      atan(sinh(y))

    Since the Mercator scale tends to infinity at +/- 90 degrees,
    there is user-defined threshold, above and below which nothing
    will be plotted.  This defaults to +/- 85 degrees.

    __ https://en.wikipedia.org/wiki/Mercator_projection
    """

Implement the MercatorLatitudeTransform class

Inside the MercatorLatitudeScale class, we will define the MercatorLatitudeTransform class that will actually transform the data. This class will inherit from mtransforms.Transform.

    class MercatorLatitudeTransform(mtransforms.Transform):
        ## There are two value members that must be defined.
        ## ``input_dims`` and ``output_dims`` specify number of input
        ## dimensions and output dimensions to the transformation.
        ## These are used by the transformation framework to do some
        ## error checking and prevent incompatible transformations from
        ## being connected together.  When defining transforms for a
        ## scale, which are, by definition, separable and have only one
        ## dimension, these members should always be set to 1.
        input_dims = output_dims = 1

        def __init__(self, thresh):
            mtransforms.Transform.__init__(self)
            self.thresh = thresh

        def transform_non_affine(self, a):
            """
            This transform takes a numpy array and returns a transformed copy.
            Since the range of the Mercator scale is limited by the
            user-specified threshold, the input array must be masked to
            contain only valid values.  Matplotlib will handle masked arrays
            and remove the out-of-range data from the plot.  However, the
            returned array *must* have the same shape as the input array, since
            these values need to remain synchronized with values in the other
            dimension.
            """
            masked = ma.masked_where((a < -self.thresh) | (a > self.thresh), a)
            if masked.mask.any():
                return ma.log(np.abs(ma.tan(masked) + 1 / ma.cos(masked)))
            else:
                return np.log(np.abs(np.tan(a) + 1 / np.cos(a)))

        def inverted(self):
            """
            Override this method so Matplotlib knows how to get the
            inverse transform for this transform.
            """
            return MercatorLatitudeScale.InvertedMercatorLatitudeTransform(
                self.thresh)

Implement the InvertedMercatorLatitudeTransform class

We will also define the InvertedMercatorLatitudeTransform class that will be used to get the inverse transform for this scale.

    class InvertedMercatorLatitudeTransform(mtransforms.Transform):
        input_dims = output_dims = 1

        def __init__(self, thresh):
            mtransforms.Transform.__init__(self)
            self.thresh = thresh

        def transform_non_affine(self, a):
            return np.arctan(np.sinh(a))

        def inverted(self):
            return MercatorLatitudeScale.MercatorLatitudeTransform(self.thresh)

Register the custom scale

We will register the custom scale with Matplotlib using mscale.register_scale.

mscale.register_scale(MercatorLatitudeScale)

Use the custom scale

Now we can use the custom scale in our plots. Here's an example of using the custom scale for latitude data in a Mercator projection.

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

    t = np.arange(-180.0, 180.0, 0.1)
    s = np.radians(t)/2.

    plt.plot(t, s, '-', lw=2)
    plt.yscale('mercator')

    plt.xlabel('Longitude')
    plt.ylabel('Latitude')
    plt.title('Mercator projection')
    plt.grid(True)

    plt.show()

Summary

In this lab, we learned how to create a custom scale in Matplotlib using the Mercator projection for latitude data. We defined the MercatorLatitudeScale and MercatorLatitudeTransform classes and registered the custom scale with Matplotlib using mscale.register_scale. We also showed an example of using the custom scale for latitude data in a Mercator projection.

Other Python Tutorials you may like