Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/contributors.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ the package wouldn't be as rich or diverse as it is today:
* Lion Krischer
* Nat Wilson
* Elliott Sales de Andrade
* Laura Dreyer


Thank you!
Expand Down
18 changes: 17 additions & 1 deletion docs/source/crs/projections.rst
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ Geostationary
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

plt.figure(figsize=(2.99893683337, 3))
plt.figure(figsize=(3, 3))
ax = plt.axes(projection=ccrs.Geostationary())
ax.coastlines(resolution='110m')
ax.gridlines()
Expand All @@ -320,6 +320,22 @@ Gnomonic
ax.gridlines()


LambertAzimuthalEqualArea
-------------------------

.. autoclass:: cartopy.crs.LambertAzimuthalEqualArea

.. plot::

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

plt.figure(figsize=(3.00670336247, 3))
ax = plt.axes(projection=ccrs.LambertAzimuthalEqualArea())
ax.coastlines(resolution='110m')
ax.gridlines()


NorthPolarStereo
----------------

Expand Down
91 changes: 78 additions & 13 deletions lib/cartopy/crs.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
__document_these__ = ['CRS', 'Geocentric', 'Geodetic', 'Globe']


WGS84_SEMIMAJOR_AXIS = 6378137.0
WGS84_SEMIMINOR_AXIS = 6356752.3142


class RotatedGeodetic(CRS):
"""
Defines a rotated latitude/longitude coordinate system with spherical
Expand Down Expand Up @@ -663,8 +667,9 @@ def __init__(self, central_longitude=0.0, globe=None):
proj4_params = [('proj', 'eqc'), ('lon_0', central_longitude)]
if globe is None:
globe = Globe(semimajor_axis=math.degrees(1))
x_max = math.radians(globe.semimajor_axis or 6378137.0) * 180
y_max = math.radians(globe.semimajor_axis or 6378137.0) * 90
a_rad = math.radians(globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
x_max = a_rad * 180
y_max = a_rad * 90
# Set the threshold around 0.5 if the x max is 180.
self._threshold = x_max / 360.
super(PlateCarree, self).__init__(proj4_params, x_max, y_max,
Expand Down Expand Up @@ -1000,8 +1005,8 @@ def y_limits(self):
GOOGLE_MERCATOR = Mercator(min_latitude=-85.0511287798066,
max_latitude=85.0511287798066,
globe=Globe(ellipse=None,
semimajor_axis=6378137,
semiminor_axis=6378137,
semimajor_axis=WGS84_SEMIMAJOR_AXIS,
semiminor_axis=WGS84_SEMIMAJOR_AXIS,
nadgrids='@null'))


Expand Down Expand Up @@ -1030,8 +1035,8 @@ def __init__(self, central_longitude=-96.0, central_latitude=39.0,
"""
Kwargs:

* central_longitude - The central longitude. Defaults to 0.
* central_latitude - The central latitude. Defaults to 0.
* central_longitude - The central longitude. Defaults to -96.
* central_latitude - The central latitude. Defaults to 39.
* false_easting - X offset from planar origin in metres.
Defaults to 0.
* false_northing - Y offset from planar origin in metres.
Expand Down Expand Up @@ -1140,6 +1145,66 @@ def y_limits(self):
return self._y_limits


class LambertAzimuthalEqualArea(Projection):
"""
A Lambert Azimuthal Equal-Area projection.

"""

def __init__(self, central_longitude=0.0, central_latitude=0.0,
false_easting=0.0, false_northing=0.0,
globe=None):
"""
Kwargs:

* central_longitude - The central longitude. Defaults to 0.
* central_latitude - The central latitude. Defaults to 0.
* false_easting - X offset from planar origin in metres.
Defaults to 0.
* false_northing - Y offset from planar origin in metres.
Defaults to 0.
* globe - A :class:`cartopy.crs.Globe`.
If omitted, a default globe is created.

"""
proj4_params = [('proj', 'laea'),
('lon_0', central_longitude),
('lat_0', central_latitude),
('x_0', false_easting),
('y_0', false_northing)]

super(LambertAzimuthalEqualArea, self).__init__(proj4_params,
globe=globe)

a = np.float(self.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
b = np.float(self.globe.semiminor_axis or WGS84_SEMIMINOR_AXIS)
lon, lat = central_longitude + 180, - central_latitude + 0.01
x, max_y = self.transform_point(lon, lat, PlateCarree())

coords = _ellipse_boundary(a * 1.9999, max_y - false_northing,
false_easting, false_northing, 61)
self._boundary = sgeom.polygon.LinearRing(coords.T)
self._x_limits = self._boundary.bounds[::2]
self._y_limits = self._boundary.bounds[1::2]
self._threshold = np.diff(self._x_limits)[0] * 1e-3

@property
def boundary(self):
return self._boundary

@property
def threshold(self):
return self._threshold

@property
def x_limits(self):
return self._x_limits

@property
def y_limits(self):
return self._y_limits


class Miller(_RectangularProjection):
def __init__(self, central_longitude=0.0):
proj4_params = [('proj', 'mill'), ('lon_0', central_longitude)]
Expand Down Expand Up @@ -1235,14 +1300,14 @@ def __init__(self, central_latitude=0.0, central_longitude=0.0,
super(Stereographic, self).__init__(proj4_params, globe=globe)

# TODO: Let the globe return the semimajor axis always.
a = np.float(self.globe.semimajor_axis or 6378137.0)
b = np.float(self.globe.semiminor_axis or 6356752.3142)
a = np.float(self.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
b = np.float(self.globe.semiminor_axis or WGS84_SEMIMINOR_AXIS)

# Note: The magic number has been picked to maintain consistent
# behaviour with a wgs84 globe. There is no guarantee that the scaling
# should even be linear.
x_axis_offset = 5e7 / 6378137.
y_axis_offset = 5e7 / 6356752.3142
x_axis_offset = 5e7 / WGS84_SEMIMAJOR_AXIS
y_axis_offset = 5e7 / WGS84_SEMIMINOR_AXIS
self._x_limits = (-a * x_axis_offset + false_easting,
a * x_axis_offset + false_easting)
self._y_limits = (-b * y_axis_offset + false_northing,
Expand Down Expand Up @@ -1295,7 +1360,7 @@ def __init__(self, central_longitude=0.0, central_latitude=0.0,
super(Orthographic, self).__init__(proj4_params, globe=globe)

# TODO: Let the globe return the semimajor axis always.
a = np.float(self.globe.semimajor_axis or 6378137.0)
a = np.float(self.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
b = np.float(self.globe.semiminor_axis or a)

if b != a:
Expand Down Expand Up @@ -1545,7 +1610,7 @@ def __init__(self, central_longitude=0.0, satellite_height=35785831,
super(Geostationary, self).__init__(proj4_params, globe=globe)

# TODO: Let the globe return the semimajor axis always.
a = np.float(self.globe.semimajor_axis or 6378137.0)
a = np.float(self.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
b = np.float(self.globe.semiminor_axis or a)
h = np.float(satellite_height)
max_x = h * math.atan(a / (a + h))
Expand Down Expand Up @@ -1686,7 +1751,7 @@ def __init__(self, central_longitude=0.0, central_latitude=0.0,
super(AzimuthalEquidistant, self).__init__(proj4_params, globe=globe)

# TODO: Let the globe return the semimajor axis always.
a = np.float(self.globe.semimajor_axis or 6378137.0)
a = np.float(self.globe.semimajor_axis or WGS84_SEMIMAJOR_AXIS)
b = np.float(self.globe.semiminor_axis or a)

coords = _ellipse_boundary(a * np.pi, b * np.pi,
Expand Down
71 changes: 71 additions & 0 deletions lib/cartopy/tests/crs/test_lambert_azimuthal_equal_area.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# (C) British Crown Copyright 2015, Met Office
#
# This file is part of cartopy.
#
# cartopy is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# cartopy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with cartopy. If not, see <http://www.gnu.org/licenses/>.

from __future__ import (absolute_import, division, print_function)

import unittest

import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import assert_equal

import cartopy.crs as ccrs


class TestLambertAzimuthalEqualArea(unittest.TestCase):
def test_default(self):
crs = ccrs.LambertAzimuthalEqualArea()
expected = ('+ellps=WGS84 +proj=laea +lon_0=0.0 '
'+lat_0=0.0 +x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)

assert_almost_equal(np.array(crs.x_limits),
[-12755636.1863, 12755636.1863],
decimal=4)
assert_almost_equal(np.array(crs.y_limits),
[-12727770.598700099, 12727770.598700099],
decimal=4)

def test_eccentric_globe(self):
globe = ccrs.Globe(semimajor_axis=1000, semiminor_axis=500,
ellipse=None)
crs = ccrs.LambertAzimuthalEqualArea(globe=globe)
expected = ('+a=1000 +b=500 +proj=laea +lon_0=0.0 +lat_0=0.0 '
'+x_0=0.0 +y_0=0.0 +no_defs')
assert_equal(crs.proj4_init, expected)

assert_almost_equal(np.array(crs.x_limits),
[-1999.9, 1999.9], decimal=1)
assert_almost_equal(np.array(crs.y_limits),
[-1380.17298647, 1380.17298647], decimal=4)

def test_offset(self):
crs = ccrs.LambertAzimuthalEqualArea()
crs_offset = ccrs.LambertAzimuthalEqualArea(false_easting=1234,
false_northing=-4321)
expected = ('+ellps=WGS84 +proj=laea +lon_0=0.0 +lat_0=0.0 '
'+x_0=1234 +y_0=-4321 +no_defs')
assert_equal(crs_offset.proj4_init, expected)
assert_equal(tuple(np.array(crs.x_limits) + 1234),
crs_offset.x_limits)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No check for y_limits here also?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(If you add the y check here, the name of the test method could do with changing, e.g. test_offsets.)

assert_equal(tuple(np.array(crs.y_limits) - 4321),
crs_offset.y_limits)


if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)