Skip to content

Commit 83e28f1

Browse files
author
Jeff Whitaker
committed
Merge pull request #104 from jswhit/master
add projection='rotpole' (rotated pole transformation)
2 parents 9eb1377 + e1c96e1 commit 83e28f1

File tree

8 files changed

+156
-6
lines changed

8 files changed

+156
-6
lines changed

Changelog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
since version 1.0.6
22
-------------------
3+
* support for rotated pole transormation (projection = 'rotpole').
34
* fix warpimage with projection = 'hammer' (issue 100).
45

56
version 1.0.6 (git tag v1.0.6rel)

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ include examples/ccsm_popgrid.nc
8888
include examples/rita.nc
8989
include examples/maskoceans.py
9090
include examples/utmtest.py
91+
include examples/test_rotpole.py
92+
include examples/wm201_Arctic_JJA_1990-2008_moyenneDesMoyennes.nc
9193
include examples/README
9294
include lib/mpl_toolkits/basemap/__init__.py
9395
include lib/mpl_toolkits/basemap/proj.py

examples/README

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,5 @@ ArcGIS server using the REST API.
164164

165165
testwmsimage.py shows how to retrieve an image from a WMS server and display
166166
it on a map.
167+
168+
test_rotpole.py shows how to plot regional climate model data in the native 'rotated pole' projection.

examples/test.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,22 @@
369369
print('plotting Square Lambert Azimuthal example ...')
370370
print(m.proj4string)
371371

372+
# create new figure
373+
fig=plt.figure()
374+
m = Basemap(projection = 'rotpole',lon_0 = -120.,\
375+
o_lon_p = 180, o_lat_p = 0,\
376+
llcrnry = -41.75, urcrnry = 37.75,\
377+
llcrnrx = 137, urcrnrx = 222.5, resolution = 'l')
378+
m.drawcoastlines()
379+
ny,nx = lons.shape
380+
m.contourf(lons[ny/2:,:],lats[ny/2:,:],topodat[ny/2:,:],50,cmap=cmap,extend='both',latlon=True)
381+
m.drawmeridians(np.arange(-180,180,20),labels=[1,1,1,1])
382+
m.drawparallels(np.arange(20,80,20))
383+
m.colorbar()
384+
plt.title('Rotated Pole',y=1.075)
385+
print('plotting Rotated Pole example ...')
386+
print(m.proj4string)
387+
372388
# create new figure
373389
fig=plt.figure()
374390
m = Basemap(lon_0=-105,boundinglat=20.,

examples/test_rotpole.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from netCDF4 import Dataset
2+
from mpl_toolkits.basemap import Basemap
3+
import numpy as np
4+
import matplotlib.pyplot as plt
5+
6+
nc = Dataset('wm201_Arctic_JJA_1990-2008_moyenneDesMoyennes.nc')
7+
lats = nc.variables['lat'][:]
8+
lons = nc.variables['lon'][:]
9+
rlats = nc.variables['rlat'][:]
10+
rlons = nc.variables['rlon'][:]
11+
rlons, rlats = np.meshgrid(rlons, rlats)
12+
data = nc.variables['air'][0,0,:,:].squeeze()
13+
data = np.ma.masked_values(data,-999.)
14+
rotpole = nc.variables['rotated_pole']
15+
16+
m = Basemap(projection='npstere',lon_0=10,boundinglat=30,resolution='c')
17+
x,y = m(lons,lats)
18+
m.drawcoastlines()
19+
m.contourf(x,y,data,20)
20+
m.drawmeridians(np.arange(-180,180,20))
21+
m.drawparallels(np.arange(20,80,20))
22+
m.colorbar()
23+
plt.title('rotated pole data in polar stere map')
24+
25+
plt.figure()
26+
# o_lon_p, o_lat_p: true lat/lon of pole in rotated coordinate system
27+
# mapping to CF metadata convention:
28+
# grid_north_pole_longitude = normalize180(180 + lon_0), where normalize180
29+
# is a function that maps to interval [-180,180].
30+
# grid_north_pole_latitude = o_lat_p
31+
# north_pole_grid_longitude = o_lon_p (optional, assumed zero if not present)
32+
m = Basemap(projection='rotpole',lon_0=rotpole.grid_north_pole_longitude-180.,\
33+
o_lon_p=rotpole.north_pole_grid_longitude,\
34+
o_lat_p=rotpole.grid_north_pole_latitude,\
35+
llcrnrlat = lats[0,0], urcrnrlat = lats[-1,-1],\
36+
llcrnrlon = lons[0,0], urcrnrlon = lons[-1,-1],resolution='c')
37+
x,y = m(lons,lats)
38+
m.drawcoastlines()
39+
m.contourf(x,y,data,20)
40+
m.drawmeridians(np.arange(-180,180,20))
41+
m.drawparallels(np.arange(20,80,20))
42+
m.colorbar()
43+
plt.title('rotated pole data in native map using real sphere corner lat/lons' )
44+
45+
plt.figure()
46+
m = Basemap(projection='rotpole',lon_0=rotpole.grid_north_pole_longitude-180.,\
47+
o_lon_p=rotpole.north_pole_grid_longitude,\
48+
o_lat_p=rotpole.grid_north_pole_latitude,\
49+
llcrnry = rlats[0,0], urcrnry = rlats[-1,-1],\
50+
llcrnrx = rlons[0,0], urcrnrx = rlons[-1,-1],resolution='c')
51+
print m.llcrnrx,m.llcrnry
52+
print m.urcrnrx,m.urcrnry
53+
x,y = m(lons,lats)
54+
m.drawcoastlines()
55+
m.contourf(x,y,data,20)
56+
m.drawmeridians(np.arange(-180,180,20))
57+
m.drawparallels(np.arange(20,80,20))
58+
m.colorbar()
59+
plt.title('rotated pole data in native map using rotated sphere corner lat/lons' )
60+
61+
plt.show()
Binary file not shown.

lib/mpl_toolkits/basemap/__init__.py

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
'vandg' : 'van der Grinten',
8989
'mbtfpq' : 'McBryde-Thomas Flat-Polar Quartic',
9090
'gnom' : 'Gnomonic',
91+
'rotpole' : 'Rotated Pole',
9192
}
9293
supported_projections = []
9394
for _items in _projnames.items():
@@ -96,6 +97,8 @@
9697

9798
_cylproj = ['cyl','merc','mill','gall','cea']
9899
_pseudocyl = ['moll','robin','eck4','kav7','sinu','mbtfpq','vandg','hammer']
100+
_dg2rad = math.radians(1.)
101+
_rad2dg = math.degrees(1.)
99102

100103
# projection specific parameters.
101104
projection_params = {'cyl' : 'corners only (no width/height)',
@@ -131,6 +134,7 @@
131134
'vandg' : 'lon_0,lat_0,no corners or width/height',
132135
'mbtfpq' : 'lon_0,lat_0,no corners or width/height',
133136
'gnom' : 'lon_0,lat_0',
137+
'rotpole' : 'lon_0,o_lat_p,o_lon_p,corner lat/lon or corner x,y (no width/height)'
134138
}
135139

136140
# create dictionary that maps epsg codes to Basemap kwargs.
@@ -258,6 +262,11 @@
258262
of the global projection). If the corners are not specified,
259263
the entire globe is plotted.
260264
265+
For ``rotpole``, the lat/lon values of the corners on the unrotated sphere
266+
may be provided as llcrnrlon,llcrnrlat,urcrnrlon,urcrnrlat, or the lat/lon
267+
values of the corners on the rotated sphere can be given as
268+
llcrnrx,llcrnry,urcrnrx,urcrnry.
269+
261270
Other keyword arguments:
262271
263272
.. tabularcolumns:: |l|L|
@@ -564,6 +573,7 @@ def __init__(self, llcrnrlon=None, llcrnrlat=None,
564573
lat_1=None, lat_2=None,
565574
lat_0=None, lon_0=None,
566575
lon_1=None, lon_2=None,
576+
o_lon_p=None, o_lat_p=None,
567577
k_0=None,
568578
no_rot=False,
569579
suppress_ticks=True,
@@ -906,6 +916,27 @@ def __init__(self, llcrnrlon=None, llcrnrlat=None,
906916
projparams['lon_0'] = lon_0
907917
else:
908918
projparams['lon_0']=0.5*(llcrnrlon+urcrnrlon)
919+
elif projection == 'rotpole':
920+
if lon_0 is None or o_lon_p is None or o_lat_p is None:
921+
msg='must specify lon_0,o_lat_p,o_lon_p for rotated pole Basemap'
922+
raise ValueError(msg)
923+
if width is not None or height is not None:
924+
sys.stdout.write('warning: width and height keywords ignored for %s projection' % _projnames[self.projection])
925+
projparams['lon_0']=lon_0
926+
projparams['o_lon_p']=o_lon_p
927+
projparams['o_lat_p']=o_lat_p
928+
projparams['o_proj']='longlat'
929+
projparams['proj']='ob_tran'
930+
if not using_corners and None in [llcrnrx,llcrnry,urcrnrx,urcrnry]:
931+
raise ValueError('must specify lat/lon values of corners in degrees')
932+
if None not in [llcrnrx,llcrnry,urcrnrx,urcrnry]:
933+
p = pyproj.Proj(projparams)
934+
llcrnrx = _dg2rad*llcrnrx; llcrnry = _dg2rad*llcrnry
935+
urcrnrx = _dg2rad*urcrnrx; urcrnry = _dg2rad*urcrnry
936+
llcrnrlon, llcrnrlat = p(llcrnrx,llcrnry,inverse=True)
937+
urcrnrlon, urcrnrlat = p(urcrnrx,urcrnry,inverse=True)
938+
self.llcrnrlon = llcrnrlon; self.llcrnrlat = llcrnrlat
939+
self.urcrnrlon = urcrnrlon; self.urcrnrlat = urcrnrlat
909940
else:
910941
raise ValueError(_unsupported_projection % projection)
911942

@@ -968,6 +999,12 @@ def __init__(self, llcrnrlon=None, llcrnrlat=None,
968999
self.urcrnrx = proj.urcrnrx
9691000
self.urcrnry = proj.urcrnry
9701001

1002+
if self.projection == 'rotpole':
1003+
lon0,lat0 = self(0.5*(self.llcrnrx + self.urcrnrx),\
1004+
0.5*(self.llcrnry + self.urcrnry),\
1005+
inverse=True)
1006+
self.projparams['lat_0']=lat0
1007+
9711008
# if ax == None, pyplot.gca may be used.
9721009
self.ax = ax
9731010
self.lsmask = None
@@ -1097,12 +1134,32 @@ def __call__(self,x,y,inverse=False):
10971134
x = 2.*lon_0-x
10981135
except TypeError:
10991136
x = [2*lon_0-xx for xx in x]
1137+
if self.projection == 'rotpole' and inverse:
1138+
try:
1139+
x = _dg2rad*x
1140+
except TypeError:
1141+
x = [_dg2rad*xx for xx in x]
1142+
try:
1143+
y = _dg2rad*y
1144+
except TypeError:
1145+
y = [_dg2rad*yy for yy in y]
11001146
xout,yout = self.projtran(x,y,inverse=inverse)
11011147
if self.celestial and inverse:
11021148
try:
11031149
xout = -2.*lon_0-xout
11041150
except:
11051151
xout = [-2.*lon_0-xx for xx in xout]
1152+
if self.projection == 'rotpole' and not inverse:
1153+
try:
1154+
xout = _rad2dg*xout
1155+
xout = np.where(xout < 0., xout+360, xout)
1156+
except TypeError:
1157+
xout = [_rad2dg*xx for xx in xout]
1158+
xout = [xx+360. if xx < 0 else xx for xx in xout]
1159+
try:
1160+
yout = _rad2dg*yout
1161+
except TypeError:
1162+
yout = [_rad2dg*yy for yy in yout]
11061163
return xout,yout
11071164

11081165
def makegrid(self,nx,ny,returnxy=False):
@@ -1155,7 +1212,8 @@ def _readboundarydata(self,name,as_polygons=False):
11551212
# coordinates, then transform back. This is
11561213
# because these projections are only defined on a hemisphere, and
11571214
# some boundary features (like Eurasia) would be undefined otherwise.
1158-
tostere = ['omerc','ortho','gnom','nsper','nplaea','npaeqd','splaea','spaeqd']
1215+
tostere =\
1216+
['omerc','ortho','gnom','nsper','nplaea','npaeqd','splaea','spaeqd']
11591217
if self.projection in tostere and name == 'gshhs':
11601218
containsPole = True
11611219
lon_0=self.projparams['lon_0']
@@ -1336,7 +1394,7 @@ def _readboundarydata(self,name,as_polygons=False):
13361394
xd = (bx[1:]-bx[0:-1])**2
13371395
yd = (by[1:]-by[0:-1])**2
13381396
dist = np.sqrt(xd+yd)
1339-
split = dist > 5000000.
1397+
split = dist > 0.5*(self.xmax-self.xmin)
13401398
if np.sum(split) and self.projection not in _cylproj:
13411399
ind = (np.compress(split,np.squeeze(split*np.indices(xd.shape)))+1).tolist()
13421400
iprev = 0

lib/mpl_toolkits/basemap/proj.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ def __init__(self,projparams,llcrnrlon,llcrnrlat,
7474
if self.projection == 'cyl':
7575
llcrnrx = llcrnrlon
7676
llcrnry = llcrnrlat
77+
elif self.projection == 'ob_tran':
78+
self._proj4 = pyproj.Proj(projparams)
79+
llcrnrx,llcrnry = self(llcrnrlon,llcrnrlat)
80+
llcrnrx = _rad2dg*llcrnrx; llcrnry = _rad2dg*llcrnry
81+
if llcrnrx < 0: llcrnrx = llcrnrx + 360
7782
elif self.projection in 'ortho':
7883
if (llcrnrlon == -180 and llcrnrlat == -90 and
7984
urcrnrlon == 180 and urcrnrlat == 90):
@@ -194,21 +199,25 @@ def __init__(self,projparams,llcrnrlon,llcrnrlat,
194199
if self.projection == 'aeqd': self._fulldisk=False
195200
# compute x_0, y_0 so ll corner of domain is x=0,y=0.
196201
# note that for 'cyl' x,y == lon,lat
197-
self.projparams['x_0']=-llcrnrx
198-
self.projparams['y_0']=-llcrnry
202+
if self.projection != 'ob_tran':
203+
self.projparams['x_0']=-llcrnrx
204+
self.projparams['y_0']=-llcrnry
199205
# reset with x_0, y_0.
200-
if self.projection != 'cyl':
206+
if self.projection not in ['cyl','ob_tran']:
201207
self._proj4 = pyproj.Proj(projparams)
202208
llcrnry = 0.
203209
llcrnrx = 0.
204-
else:
210+
elif self.projection != 'ob_tran':
205211
llcrnrx = llcrnrlon
206212
llcrnry = llcrnrlat
207213
if urcrnrislatlon:
208214
self.urcrnrlon = urcrnrlon
209215
self.urcrnrlat = urcrnrlat
210216
if self.projection not in ['ortho','geos','nsper','aeqd'] + _pseudocyl:
211217
urcrnrx,urcrnry = self(urcrnrlon,urcrnrlat)
218+
if self.projection == 'ob_tran':
219+
urcrnrx = _rad2dg*urcrnrx; urcrnry = _rad2dg*urcrnry
220+
if urcrnrx < 0: urcrnrx = urcrnrx + 360
212221
elif self.projection in ['ortho','geos','nsper','aeqd']:
213222
if self._fulldisk:
214223
urcrnrx = 2.*self._width
@@ -226,6 +235,7 @@ def __init__(self,projparams,llcrnrlon,llcrnrlat,
226235
urcrnrlon, urcrnrlat = self(urcrnrx, urcrnry, inverse=True)
227236
self.urcrnrlon = urcrnrlon
228237
self.urcrnrlat = urcrnrlat
238+
229239
# corners of domain.
230240
self.llcrnrx = llcrnrx
231241
self.llcrnry = llcrnry

0 commit comments

Comments
 (0)