Revision: 4776
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4776&view=rev
Author:   mdboom
Date:     2007-12-20 05:00:20 -0800 (Thu, 20 Dec 2007)

Log Message:
-----------
Add very preliminary and experimental support for some geo projections.

Modified Paths:
--------------
    branches/transforms/lib/matplotlib/projections/__init__.py

Added Paths:
-----------
    branches/transforms/examples/geo_demo.py
    branches/transforms/lib/matplotlib/projections/geo.py

Added: branches/transforms/examples/geo_demo.py
===================================================================
--- branches/transforms/examples/geo_demo.py                            (rev 0)
+++ branches/transforms/examples/geo_demo.py    2007-12-20 13:00:20 UTC (rev 
4776)
@@ -0,0 +1,16 @@
+import numpy as npy
+npy.seterr("raise")
+
+from pylab import *
+
+subplot(221, projection="aitoff")
+grid(True)
+
+subplot(222, projection="hammer")
+grid(True)
+
+subplot(223, projection="lambert")
+grid(True)
+
+
+show()

Modified: branches/transforms/lib/matplotlib/projections/__init__.py
===================================================================
--- branches/transforms/lib/matplotlib/projections/__init__.py  2007-12-20 
12:49:14 UTC (rev 4775)
+++ branches/transforms/lib/matplotlib/projections/__init__.py  2007-12-20 
13:00:20 UTC (rev 4776)
@@ -1,3 +1,4 @@
+from geo import AitoffAxes, HammerAxes, MolleweideAxes, LambertAxes
 from polar import PolarAxes
 from matplotlib import axes
 
@@ -21,7 +22,11 @@
 
 projection_registry.register(
     axes.Axes,
-    PolarAxes)
+    PolarAxes,
+    AitoffAxes,
+    HammerAxes,
+    MolleweideAxes,
+    LambertAxes)
 
 def get_projection_class(projection):
     if projection is None:

Added: branches/transforms/lib/matplotlib/projections/geo.py
===================================================================
--- branches/transforms/lib/matplotlib/projections/geo.py                       
        (rev 0)
+++ branches/transforms/lib/matplotlib/projections/geo.py       2007-12-20 
13:00:20 UTC (rev 4776)
@@ -0,0 +1,597 @@
+import math
+
+import numpy as npy
+from matplotlib.numerix import npyma as ma
+
+import matplotlib
+rcParams = matplotlib.rcParams
+from matplotlib.artist import kwdocd
+from matplotlib.axes import Axes
+from matplotlib import cbook
+from matplotlib.patches import Circle
+from matplotlib.path import Path
+from matplotlib.ticker import Formatter, Locator, NullLocator, FixedLocator, 
NullFormatter
+from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \
+    BboxTransformTo, IdentityTransform, Transform, TransformWrapper
+
+class GeoAxes(Axes):
+    """
+    An abstract base class for geographic projections
+    """
+    class ThetaFormatter(Formatter):
+        """
+        Used to format the theta tick labels.  Converts the native
+        unit of radians into degrees and adds a degree symbol.
+        """
+        def __init__(self, round_to=1.0):
+            self._round_to = round_to
+
+        def __call__(self, x, pos=None):
+            degrees = (x / npy.pi) * 180.0
+            degrees = round(degrees / self._round_to) * self._round_to
+            # \u00b0 : degree symbol
+            return u"%d\u00b0" % degrees
+
+    RESOLUTION = 75
+
+    def cla(self):
+        Axes.cla(self)
+
+        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.grid(rcParams['axes.grid'])
+
+        Axes.set_xlim(self, -npy.pi, npy.pi)
+        Axes.set_ylim(self, -npy.pi / 2.0, npy.pi / 2.0)
+
+    def _set_lim_and_transforms(self):
+       self.dataLim = Bbox.unit()
+        self.viewLim = Bbox.unit()
+        self.transAxes = BboxTransformTo(self.bbox)
+
+        # Transforms the x and y axis separately by a scale factor
+        # It is assumed that this part will have non-linear components
+        self.transScale = TransformWrapper(IdentityTransform())
+
+        # A (possibly non-linear) projection on the (already scaled) data
+        self.transProjection = self._get_core_transform(self.RESOLUTION)
+
+        self.transAffine = self._get_affine_transform()
+
+        # The complete data transformation stack -- from data all the
+        # way to display coordinates
+        self.transData = \
+            self.transProjection + \
+            self.transAffine + \
+            self.transAxes
+
+        # This is the transform for longitude ticks.
+        self._xaxis_pretransform = \
+            Affine2D() \
+            .scale(1.0, self._longitude_cap * 2.0) \
+            .translate(0.0, -self._longitude_cap)
+        self._xaxis_transform = \
+            self._xaxis_pretransform + \
+            self.transData
+        self._xaxis_text1_transform = \
+            Affine2D().scale(1.0, 0.0) + \
+            self.transData + \
+            Affine2D().translate(0.0, 4.0)
+        self._xaxis_text2_transform = \
+            Affine2D().scale(1.0, 0.0) + \
+            self.transData + \
+            Affine2D().translate(0.0, -4.0)
+
+        # This is the transform for latitude ticks.
+        yaxis_stretch = Affine2D().scale(npy.pi * 2.0, 1.0).translate(-npy.pi, 
0.0)
+        yaxis_space = Affine2D().scale(1.0, 1.1)
+        self._yaxis_transform = \
+            yaxis_stretch + \
+            self.transData
+        yaxis_text_base = \
+            yaxis_stretch + \
+            self.transProjection + \
+            (yaxis_space + \
+             self.transAffine + \
+             self.transAxes)
+        self._yaxis_text1_transform = \
+            yaxis_text_base + \
+            Affine2D().translate(-8.0, 0.0)
+        self._yaxis_text2_transform = \
+            yaxis_text_base + \
+            Affine2D().translate(8.0, 0.0)
+
+    def _get_affine_transform(self):
+        transform = self._get_core_transform(1)
+        xscale, _ = transform.transform_point((npy.pi, 0))
+        _, yscale = transform.transform_point((0, npy.pi / 2.0))
+        return Affine2D() \
+            .scale(0.5 / xscale, 0.5 / yscale) \
+            .translate(0.5, 0.5)
+
+    def update_layout(self, renderer):
+        t_text, b_text = self.xaxis.get_text_heights(renderer)
+        l_text, r_text = self.yaxis.get_text_widths(renderer)
+        originalPosition = self.get_position(True)
+        title_offset = (b_text - originalPosition.transformed(
+                self.figure.transFigure).height) / 2.0
+        self.titleOffsetTrans.clear().translate(0, title_offset)
+
+    def get_xaxis_transform(self):
+        return self._xaxis_transform
+
+    def get_xaxis_text1_transform(self, pixelPad):
+        return self._xaxis_text1_transform, 'bottom', 'center'
+
+    def get_xaxis_text2_transform(self, pixelPad):
+        return self._xaxis_text2_transform, 'top', 'center'
+
+    def get_yaxis_transform(self):
+        return self._yaxis_transform
+
+    def get_yaxis_text1_transform(self, pixelPad):
+        return self._yaxis_text1_transform, 'center', 'right'
+
+    def get_yaxis_text2_transform(self, pixelPad):
+        return self._yaxis_text2_transform, 'center', 'left'
+
+    def get_axes_patch(self):
+        return Circle((0.5, 0.5), 0.5)
+
+    def set_yscale(self, *args, **kwargs):
+        if args[0] != 'linear':
+            raise NotImplementedError
+
+    set_xscale = set_yscale
+
+    def set_xlim(self, *args, **kwargs):
+        Axes.set_xlim(self, -npy.pi, npy.pi)
+        Axes.set_ylim(self, -npy.pi / 2.0, npy.pi / 2.0)
+
+    set_ylim = set_xlim
+
+    def format_coord(self, long, lat):
+        'return a format string formatting the coordinate'
+        long = long * (180.0 / npy.pi)
+        lat = lat * (180.0 / npy.pi)
+        if lat >= 0.0:
+            ns = 'N'
+        else:
+            ns = 'S'
+        if long >= 0.0:
+            ew = 'E'
+        else:
+            ew = 'W'
+        return u'%f\u00b0%s, %f\u00b0%s' % (abs(lat), ns, abs(long), ew)
+
+    def set_longitude_grid(self, degrees):
+        """
+        Set the number of degrees between each longitude grid.
+        """
+        number = (360.0 / degrees) + 1
+        self.xaxis.set_major_locator(
+            FixedLocator(
+                npy.linspace(-npy.pi, npy.pi, number, True)[1:-1]))
+        self._logitude_degrees = degrees
+        self.xaxis.set_major_formatter(self.ThetaFormatter(degrees))
+
+    def set_latitude_grid(self, degrees):
+        """
+        Set the number of degrees between each longitude grid.
+        """
+        number = (180.0 / degrees) + 1
+        self.yaxis.set_major_locator(
+            FixedLocator(
+                npy.linspace(-npy.pi / 2.0, npy.pi / 2.0, number, True)[1:-1]))
+        self._latitude_degrees = degrees
+        self.yaxis.set_major_formatter(self.ThetaFormatter(degrees))
+
+    def set_longitude_grid_ends(self, degrees):
+        """
+        Set the latitude(s) at which to stop drawing the longitude grids.
+        """
+        self._longitude_cap = degrees * (npy.pi / 180.0)
+        self._xaxis_pretransform \
+            .clear() \
+            .scale(1.0, self._longitude_cap * 2.0) \
+            .translate(0.0, -self._longitude_cap)
+
+    def get_data_ratio(self):
+        '''
+        Return the aspect ratio of the data itself.
+        '''
+        return 1.0
+
+    ### Interactive panning
+
+    def can_zoom(self):
+        """
+        Return True if this axes support the zoom box
+        """
+        return False
+
+    def start_pan(self, x, y, button):
+        pass
+
+    def end_pan(self):
+        pass
+
+    def drag_pan(self, button, key, x, y):
+        pass
+
+
+class AitoffAxes(GeoAxes):
+    name = 'aitoff'
+
+    class AitoffTransform(Transform):
+        """
+        The base Aitoff transform.
+        """
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            """
+            Create a new Aitoff transform.  Resolution is the number of steps
+            to interpolate between each input line segment to approximate its
+            path in curved Aitoff space.
+            """
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, ll):
+            longitude = ll[:, 0:1]
+            latitude  = ll[:, 1:2]
+
+            # Pre-compute some values
+            half_long = longitude / 2.0
+            cos_latitude = npy.cos(latitude)
+
+            alpha = npy.arccos(cos_latitude * npy.cos(half_long))
+            # Mask this array, or we'll get divide-by-zero errors
+            alpha = ma.masked_where(alpha == 0.0, alpha)
+            # We want unnormalized sinc.  numpy.sinc gives us normalized
+            sinc_alpha = ma.sin(alpha) / alpha
+
+            x = (cos_latitude * npy.sin(half_long)) / sinc_alpha
+            y = (npy.sin(latitude) / sinc_alpha)
+            x.set_fill_value(0.0)
+            y.set_fill_value(0.0)
+            return npy.concatenate((x.filled(), y.filled()), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        transform_non_affine = transform
+        transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+
+        def transform_path(self, path):
+            vertices = path.vertices
+            ipath = path.interpolated(self._resolution)
+            return Path(self.transform(ipath.vertices), ipath.codes)
+        transform_path.__doc__ = Transform.transform_path.__doc__
+
+        transform_path_non_affine = transform_path
+        transform_path_non_affine.__doc__ = 
Transform.transform_path_non_affine.__doc__
+
+        def inverted(self):
+            return AitoffAxes.InvertedAitoffTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    class InvertedAitoffTransform(Transform):
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, xy):
+            # MGDTODO: Math is hard ;(
+            return xy
+        transform.__doc__ = Transform.transform.__doc__
+
+        def inverted(self):
+            return AitoffAxes.AitoffTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    def __init__(self, *args, **kwargs):
+        self._longitude_cap = npy.pi / 2.0
+        GeoAxes.__init__(self, *args, **kwargs)
+        self.set_aspect(0.5, adjustable='box', anchor='C')
+        self.cla()
+
+    def _get_core_transform(self, resolution):
+        return self.AitoffTransform(resolution)
+
+
+class HammerAxes(GeoAxes):
+    name = 'hammer'
+
+    class HammerTransform(Transform):
+        """
+        The base Hammer transform.
+        """
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            """
+            Create a new Hammer transform.  Resolution is the number of steps
+            to interpolate between each input line segment to approximate its
+            path in curved Hammer space.
+            """
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, ll):
+            longitude = ll[:, 0:1]
+            latitude  = ll[:, 1:2]
+
+            # Pre-compute some values
+            half_long = longitude / 2.0
+            cos_latitude = npy.cos(latitude)
+            sqrt2 = npy.sqrt(2.0)
+
+            alpha = 1.0 + cos_latitude * npy.cos(half_long)
+            x = (2.0 * sqrt2) * (cos_latitude * npy.sin(half_long)) / alpha
+            y = (sqrt2 * npy.sin(latitude)) / alpha
+            return npy.concatenate((x, y), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        transform_non_affine = transform
+        transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+
+        def transform_path(self, path):
+            vertices = path.vertices
+            ipath = path.interpolated(self._resolution)
+            return Path(self.transform(ipath.vertices), ipath.codes)
+        transform_path.__doc__ = Transform.transform_path.__doc__
+
+        transform_path_non_affine = transform_path
+        transform_path_non_affine.__doc__ = 
Transform.transform_path_non_affine.__doc__
+
+        def inverted(self):
+            return HammerAxes.InvertedHammerTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    class InvertedHammerTransform(Transform):
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, xy):
+            x = xy[:, 0:1]
+            y = xy[:, 1:2]
+
+            quarter_x = 0.25 * x
+            half_y = 0.5 * y
+            z = npy.sqrt(1.0 - quarter_x*quarter_x - half_y*half_y)
+            longitude = 2 * npy.arctan((z*x) / (2.0 * (2.0*z*z - 1.0)))
+            latitude = npy.arcsin(y*z)
+            return npy.concatenate((longitude, latitude), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        def inverted(self):
+            return HammerAxes.HammerTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    def __init__(self, *args, **kwargs):
+        self._longitude_cap = npy.pi / 2.0
+        GeoAxes.__init__(self, *args, **kwargs)
+        self.set_aspect(0.5, adjustable='box', anchor='C')
+        self.cla()
+
+    def _get_core_transform(self, resolution):
+        return self.HammerTransform(resolution)
+
+
+class MolleweideAxes(GeoAxes):
+    name = 'molleweide'
+
+    class MolleweideTransform(Transform):
+        """
+        The base Molleweide transform.
+        """
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            """
+            Create a new Molleweide transform.  Resolution is the number of 
steps
+            to interpolate between each input line segment to approximate its
+            path in curved Molleweide space.
+            """
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, ll):
+            longitude = ll[:, 0:1]
+            latitude  = ll[:, 1:2]
+
+            aux = 2.0 * npy.arcsin((2.0 * latitude) / npy.pi)
+            x = (2.0 * npy.sqrt(2.0) * longitude * npy.cos(aux)) / npy.pi
+            y = (npy.sqrt(2.0) * npy.sin(aux))
+
+            return npy.concatenate((x, y), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        transform_non_affine = transform
+        transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+
+        def transform_path(self, path):
+            vertices = path.vertices
+            ipath = path.interpolated(self._resolution)
+            return Path(self.transform(ipath.vertices), ipath.codes)
+        transform_path.__doc__ = Transform.transform_path.__doc__
+
+        transform_path_non_affine = transform_path
+        transform_path_non_affine.__doc__ = 
Transform.transform_path_non_affine.__doc__
+
+        def inverted(self):
+            return MolleweideAxes.InvertedMolleweideTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    class InvertedMolleweideTransform(Transform):
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, resolution):
+            Transform.__init__(self)
+            self._resolution = resolution
+
+        def transform(self, xy):
+            # MGDTODO: Math is hard ;(
+            return xy
+        transform.__doc__ = Transform.transform.__doc__
+
+        def inverted(self):
+            return MolleweideAxes.MolleweideTransform(self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    def __init__(self, *args, **kwargs):
+        self._longitude_cap = npy.pi / 2.0
+        GeoAxes.__init__(self, *args, **kwargs)
+        self.set_aspect(0.5, adjustable='box', anchor='C')
+        self.cla()
+
+    def _get_core_transform(self, resolution):
+        return self.MolleweideTransform(resolution)
+
+
+class LambertAxes(GeoAxes):
+    name = 'lambert'
+
+    class LambertTransform(Transform):
+        """
+        The base Lambert transform.
+        """
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, center_longitude, center_latitude, resolution):
+            """
+            Create a new Lambert transform.  Resolution is the number of steps
+            to interpolate between each input line segment to approximate its
+            path in curved Lambert space.
+            """
+            Transform.__init__(self)
+            self._resolution = resolution
+            self._center_longitude = center_longitude
+            self._center_latitude = center_latitude
+
+        def transform(self, ll):
+            longitude = ll[:, 0:1]
+            latitude  = ll[:, 1:2]
+            clong = self._center_longitude
+            clat = self._center_latitude
+            cos_lat = npy.cos(latitude)
+            sin_lat = npy.sin(latitude)
+            diff_long = longitude - clong
+            cos_diff_long = npy.cos(diff_long)
+
+            inner_k = (1.0 +
+                       npy.sin(clat)*sin_lat +
+                       npy.cos(clat)*cos_lat*cos_diff_long)
+            # Prevent divide-by-zero problems
+            inner_k = npy.where(inner_k == 0.0, 1e-15, inner_k)
+            k = npy.sqrt(2.0 / inner_k)
+            x = k*cos_lat*npy.sin(diff_long)
+            y = k*(npy.cos(clat)*sin_lat -
+                   npy.sin(clat)*cos_lat*cos_diff_long)
+
+            return npy.concatenate((x, y), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        transform_non_affine = transform
+        transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__
+
+        def transform_path(self, path):
+            vertices = path.vertices
+            ipath = path.interpolated(self._resolution)
+            return Path(self.transform(ipath.vertices), ipath.codes)
+        transform_path.__doc__ = Transform.transform_path.__doc__
+
+        transform_path_non_affine = transform_path
+        transform_path_non_affine.__doc__ = 
Transform.transform_path_non_affine.__doc__
+
+        def inverted(self):
+            return LambertAxes.InvertedLambertTransform(
+                self._center_longitude,
+                self._center_latitude,
+                self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    class InvertedLambertTransform(Transform):
+        input_dims = 2
+        output_dims = 2
+        is_separable = False
+
+        def __init__(self, center_longitude, center_latitude, resolution):
+            Transform.__init__(self)
+            self._resolution = resolution
+            self._center_longitude = center_longitude
+            self._center_latitude = center_latitude
+
+        def transform(self, xy):
+            x = xy[:, 0:1]
+            y = xy[:, 1:2]
+            clong = self._center_longitude
+            clat = self._center_latitude
+            p = npy.sqrt(x*x + y*y)
+            p = npy.where(p == 0.0, 1e-9, p)
+            c = 2.0 * npy.arcsin(0.5 * p)
+            sin_c = npy.sin(c)
+            cos_c = npy.cos(c)
+
+            lat = npy.arcsin(cos_c*npy.sin(clat) +
+                             ((y*sin_c*npy.cos(clat)) / p))
+            long = clong + npy.arctan(
+                (x*sin_c) / (p*npy.cos(clat)*cos_c - y*npy.sin(clat)*sin_c))
+
+            return npy.concatenate((long, lat), 1)
+        transform.__doc__ = Transform.transform.__doc__
+
+        def inverted(self):
+            return LambertAxes.LambertTransform(
+                self._center_longitude,
+                self._center_latitude,
+                self._resolution)
+        inverted.__doc__ = Transform.inverted.__doc__
+
+    def __init__(self, *args, **kwargs):
+        self._longitude_cap = npy.pi / 2.0
+        self._center_longitude = kwargs.pop("center_longitude", 0.0)
+        self._center_latitude = kwargs.pop("center_latitude", 0.0)
+        GeoAxes.__init__(self, *args, **kwargs)
+        self.set_aspect('equal', adjustable='box', anchor='C')
+        self.cla()
+
+    def cla(self):
+        GeoAxes.cla(self)
+        self.yaxis.set_major_formatter(NullFormatter())
+
+    def _get_core_transform(self, resolution):
+        return self.LambertTransform(
+            self._center_longitude,
+            self._center_latitude,
+            resolution)
+
+    def _get_affine_transform(self):
+        return Affine2D() \
+            .scale(0.25) \
+            .translate(0.5, 0.5)


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
SF.Net email is sponsored by:
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services
for just about anything Open Source.
http://ad.doubleclick.net/clk;164216239;13503038;w?http://sf.net/marketplace
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to