forked from 3rdparty/wrf-python
28 changed files with 1726 additions and 294 deletions
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
|
||||
try: |
||||
from xarray import DataArray |
||||
except ImportError: |
||||
_XARRAY_ENABLED = False |
||||
else: |
||||
_XARRAY_ENABLED = True |
||||
|
||||
try: |
||||
from cartopy import crs |
||||
except ImportError: |
||||
_CARTOPY_ENABLED = False |
||||
else: |
||||
_CARTOPY_ENABLED = True |
||||
|
||||
try: |
||||
from mpl_toolkits.basemap import Basemap |
||||
except ImportError: |
||||
_BASEMAP_ENABLED = False |
||||
else: |
||||
_BASEMAP_ENABLED = True |
||||
|
||||
try: |
||||
from Ngl import Resources |
||||
except ImportError: |
||||
_PYNGL_ENABLED = False |
||||
else: |
||||
_PYNGL_ENABLED = True |
||||
|
||||
__all__ = ["xarray_enabled", "disable_xarray", "enable_xarray", |
||||
"cartopy_enabled", "enable_cartopy", "enable_cartopy", |
||||
"basemap_enabled", "disable_basemap", "enable_basemap", |
||||
"pyngl_enabled", "enable_pyngl", "disable_pyngl"] |
||||
|
||||
def xarray_enabled(): |
||||
global _XARRAY_ENABLED |
||||
return _XARRAY_ENABLED |
||||
|
||||
def disable_xarray(): |
||||
global _XARRAY_ENABLED |
||||
_XARRAY_ENABLED = False |
||||
|
||||
def enable_xarray(): |
||||
global _XARRAY_ENABLED |
||||
_XARRAY_ENABLED = True |
||||
|
||||
def cartopy_enabled(): |
||||
global _CARTOPY_ENABLED |
||||
return _CARTOPY_ENABLED |
||||
|
||||
def enable_cartopy(): |
||||
global _CARTOPY_ENABLED |
||||
_CARTOPY_ENABLED = True |
||||
|
||||
def disable_cartopy(): |
||||
global _CARTOPY_ENABLED |
||||
_CARTOPY_ENABLED = True |
||||
|
||||
def basemap_enabled(): |
||||
global _BASEMAP_ENABLED |
||||
return _BASEMAP_ENABLED |
||||
|
||||
def enable_basemap(): |
||||
global _BASEMAP_ENABLED |
||||
_BASEMAP_ENABLED = True |
||||
|
||||
def disable_basemap(): |
||||
global _BASEMAP_ENABLED |
||||
_BASEMAP_ENABLED = True |
||||
|
||||
def pyngl_enabled(): |
||||
global _PYNGL_ENABLED |
||||
return _PYNGL_ENABLED |
||||
|
||||
def enable_pyngl(): |
||||
global _PYNGL_ENABLED |
||||
_PYNGL_ENABLED = True |
||||
|
||||
def disable_pyngl(): |
||||
global _PYNGL_ENABLED |
||||
_PYNGL_ENABLED = True |
@ -0,0 +1,748 @@
@@ -0,0 +1,748 @@
|
||||
import numpy as np |
||||
import math |
||||
|
||||
from wrf.var.config import basemap_enabled, cartopy_enabled, pyngl_enabled |
||||
from wrf.var.constants import Constants |
||||
|
||||
if cartopy_enabled(): |
||||
from cartopy import crs |
||||
|
||||
if basemap_enabled(): |
||||
from mpl_toolkits.basemap import Basemap |
||||
|
||||
if pyngl_enabled(): |
||||
from Ngl import Resources |
||||
|
||||
__all__ = ["WrfProj", "LambertConformalProj", "MercatorProj", |
||||
"PolarStereographicProj", "LatLonProj", "RotLatLonProj", |
||||
"getproj"] |
||||
|
||||
|
||||
if cartopy_enabled(): |
||||
class MercatorWithLatTsProj(crs.Mercator): |
||||
def __init__(self, central_longitude=0.0, |
||||
latitude_true_scale=0.0, |
||||
min_latitude=-80.0, |
||||
max_latitude=84.0, |
||||
globe=None): |
||||
proj4_params = [("proj", "merc"), |
||||
("lon_0", central_longitude), |
||||
("lat_ts", latitude_true_scale), |
||||
("k", 1), |
||||
("units", "m")] |
||||
super(crs.Mercator, self).__init__(proj4_params, globe=globe) |
||||
|
||||
# Calculate limits. |
||||
limits = self.transform_points(crs.Geodetic(), |
||||
np.array([-180, 180]) + central_longitude, |
||||
np.array([min_latitude, max_latitude])) |
||||
|
||||
# When using a latitude of true scale, the min/max x-limits get set |
||||
# to the same value, so make sure the left one is negative |
||||
xlimits = limits[..., 0] |
||||
|
||||
if xlimits[0] == xlimits[1]: |
||||
if xlimits[0] < 0: |
||||
xlimits[1] = -xlimits[1] |
||||
else: |
||||
xlimits[0] = -xlimits[0] |
||||
|
||||
self._xlimits = tuple(xlimits) |
||||
self._ylimits = tuple(limits[..., 1]) |
||||
|
||||
self._threshold = np.diff(self.x_limits)[0] / 720 |
||||
|
||||
def _ismissing(val): |
||||
return val is None or val > 90. or val < -90. |
||||
|
||||
class WrfProj(object): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
if bottom_left is not None and top_right is not None: |
||||
self.ll_lat = bottom_left[0] |
||||
self.ll_lon = bottom_left[1] |
||||
self.ur_lat = top_right[0] |
||||
self.ur_lon = top_right[1] |
||||
self.bottom_left = bottom_left |
||||
self.top_right = top_right |
||||
elif lats is not None and lons is not None: |
||||
self.ll_lat = lats[0,0] |
||||
self.ur_lat = lats[-1,-1] |
||||
self.ll_lon = lons[0,0] |
||||
self.ur_lon = lons[-1,-1] |
||||
self.bottom_left = [self.ll_lat, self.ll_lon] |
||||
self.top_right = [self.ur_lat, self.ur_lon] |
||||
else: |
||||
raise ValueError("invalid corner point arguments") |
||||
|
||||
# These indicate the center of the nest/domain, not necessarily the |
||||
# center of the projection |
||||
self._cen_lat = proj_params.get("CEN_LAT", None) |
||||
self._cen_lon = proj_params.get("CEN_LON", None) |
||||
|
||||
self.truelat1 = proj_params.get("TRUELAT1", None) |
||||
self.truelat2 = (proj_params.get("TRUELAT2", None) |
||||
if not _ismissing(proj_params.get("TRUELAT2", None)) |
||||
else None) |
||||
self.moad_cen_lat = proj_params.get("MOAD_CEN_LAT", None) |
||||
self.stand_lon = proj_params.get("STAND_LON", None) |
||||
self.pole_lat = proj_params.get("POLE_LAT", None) |
||||
self.pole_lon = proj_params.get("POLE_LON", None) |
||||
|
||||
# Just in case... |
||||
if self.moad_cen_lat is None: |
||||
self.moad_cen_lat = self._cen_lat |
||||
|
||||
if self.stand_lon is None: |
||||
self.stand_lon = self._cen_lon |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
return None |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
return None |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
return None |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
return ([self.ll_lon, self.ur_lon], [self.ll_lat, self.ur_lat]) |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
return None |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
return None |
||||
|
||||
@property |
||||
def _globe(self): |
||||
return (None if not cartopy_enabled() |
||||
else crs.Globe(ellipse=None, |
||||
semimajor_axis=Constants.WRF_EARTH_RADIUS, |
||||
semiminor_axis=Constants.WRF_EARTH_RADIUS)) |
||||
|
||||
def cartopy_xlim(self): |
||||
"""Return the x extents in projected coordinates (for cartopy)""" |
||||
return self._cart_extents[0] |
||||
|
||||
def cartopy_ylim(self): |
||||
"""Return the y extents in projected coordinates (for cartopy)""" |
||||
return self._cart_extents[1] |
||||
|
||||
def __repr__(self): |
||||
args = ("bottom_left={}, top_right={}, " |
||||
"stand_lon={}, moad_cen_lat={}, " |
||||
"pole_lat={}, pole_lon={}".format((self.ll_lat, self.ll_lon), |
||||
(self.ur_lat, self.ur_lon), |
||||
self.stand_lon, |
||||
self.moad_cen_lat, |
||||
self.pole_lat, |
||||
self.pole_lon)) |
||||
return "{}({})".format(self.__class__.__name__, args) |
||||
|
||||
def basemap(self): |
||||
"""Return a mpl_toolkits.basemap.Basemap instance for the |
||||
projection""" |
||||
if not basemap_enabled(): |
||||
raise RuntimeError("'mpl_toolkits.basemap' is not " |
||||
"installed or is disabled") |
||||
return self._basemap |
||||
|
||||
def cartopy(self): |
||||
"""Return a cartopy.crs.Projection subclass for the |
||||
projection""" |
||||
if not cartopy_enabled(): |
||||
raise RuntimeError("'cartopy' is not " |
||||
"installed or is disabled") |
||||
return self._cartopy |
||||
|
||||
def pyngl(self): |
||||
"""Return the PyNGL resources for the projection""" |
||||
if not pyngl_enabled(): |
||||
raise RuntimeError("'pyngl' is not " |
||||
"installed or is disabled") |
||||
return self._pyngl |
||||
|
||||
def proj4(self): |
||||
"""Return the proj4 string for the map projection""" |
||||
return self._proj4 |
||||
|
||||
def cf(self): |
||||
"""Return a dictionary with the NetCDF CF parameters for the |
||||
projection""" |
||||
return self._cf_params |
||||
|
||||
|
||||
class LambertConformalProj(WrfProj): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
super(LambertConformalProj, self).__init__(bottom_left, |
||||
top_right, lats, lons, **proj_params) |
||||
|
||||
self._std_parallels = [self.truelat1] |
||||
if self.truelat2 is not None: |
||||
self._std_parallels.append(self.truelat2) |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
_cf_params = {} |
||||
_cf_params["grid_mapping_name"] = "lambert_conformal_conic"; |
||||
_cf_params["standard_parallel"] = self._std_parallels |
||||
_cf_params["longitude_of_central_meridian"] = self.stand_lon |
||||
_cf_params["latitude_of_projection_origin"] = self.moad_cen_lat |
||||
_cf_params["semi_major_axis"] = Constants.WRF_EARTH_RADIUS |
||||
|
||||
return _cf_params |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
if not pyngl_enabled(): |
||||
return None |
||||
|
||||
truelat2 = (self.truelat1 |
||||
if _ismissing(self.truelat2) |
||||
else self.truelat2) |
||||
|
||||
_pyngl = Resources() |
||||
_pyngl.mpProjection = "LambertConformal" |
||||
_pyngl.mpDataBaseVersion = "MediumRes" |
||||
_pyngl.mpLimitMode = "Corners" |
||||
_pyngl.mpLeftCornerLonF = self.ll_lon |
||||
_pyngl.mpLeftCornerLatF = self.ll_lat |
||||
_pyngl.mpRightCornerLonF = self.ur_lon |
||||
_pyngl.mpRightCornerLatF = self.ur_lat |
||||
_pyngl.mpLambertMeridianF = self.stand_lon |
||||
_pyngl.mpLambertParallel1F = self.truelat1 |
||||
_pyngl.mpLambertParallel2F = truelat2 |
||||
|
||||
return _pyngl |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
if not basemap_enabled(): |
||||
return None |
||||
|
||||
_basemap = Basemap(projection = "lcc", |
||||
lon_0 = self.stand_lon, |
||||
lat_0 = self.moad_cen_lat, |
||||
lat_1 = self.truelat1, |
||||
lat_2 = self.truelat2, |
||||
llcrnrlat = self.ll_lat, |
||||
urcrnrlat = self.ur_lat, |
||||
llcrnrlon = self.ll_lon, |
||||
urcrnrlon = self.ur_lon, |
||||
rsphere = Constants.WRF_EARTH_RADIUS, |
||||
resolution = 'l') |
||||
|
||||
return _basemap |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
if not cartopy_enabled(): |
||||
return None |
||||
|
||||
_cartopy = crs.LambertConformal( |
||||
central_longitude = self.stand_lon, |
||||
central_latitude = self.moad_cen_lat, |
||||
standard_parallels = self._std_parallels, |
||||
globe = self._globe) |
||||
|
||||
return _cartopy |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
# Need to modify the extents for the new projection |
||||
pc = crs.PlateCarree() |
||||
xs, ys, zs = self._cartopy.transform_points(pc, |
||||
np.array([self.ll_lon, self.ur_lon]), |
||||
np.array([self.ll_lat, self.ur_lat])).T |
||||
|
||||
|
||||
_xlimits = xs.tolist() |
||||
_ylimits = ys.tolist() |
||||
|
||||
return (_xlimits, _ylimits) |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
truelat2 = (self.truelat1 |
||||
if _ismissing(self.truelat2) |
||||
else self.truelat2) |
||||
|
||||
_proj4 = ("+proj=lcc +units=meters +a={} +b={} +lat_1={} " |
||||
"+lat_2={} +lat_0={} +lon_0={}".format( |
||||
Constants.WRF_EARTH_RADIUS, |
||||
Constants.WRF_EARTH_RADIUS, |
||||
self.truelat1, |
||||
truelat2, |
||||
self.moad_cen_lat, |
||||
self.stand_lon)) |
||||
return _proj4 |
||||
|
||||
class MercatorProj(WrfProj): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
super(MercatorProj, self).__init__(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
|
||||
self._lat_ts = (None |
||||
if self.truelat1 == 0. or _ismissing(self.truelat1) |
||||
else self.truelat1) |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
|
||||
_cf_params = {} |
||||
_cf_params["grid_mapping_name"] = "mercator" |
||||
_cf_params["longitude_of_projection_origin"] = self.stand_lon |
||||
_cf_params["standard_parallel"] = self.truelat1 |
||||
|
||||
return _cf_params |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
if not pyngl_enabled(): |
||||
return None |
||||
|
||||
_pyngl = Resources() |
||||
_pyngl.mpProjection = "Mercator" |
||||
_pyngl.mpDataBaseVersion = "MediumRes" |
||||
_pyngl.mpLimitMode = "Corners" |
||||
_pyngl.mpLeftCornerLonF = self.ll_lon |
||||
_pyngl.mpLeftCornerLatF = self.ll_lat |
||||
_pyngl.mpRightCornerLonF = self.ur_lon |
||||
_pyngl.mpRightCornerLatF = self.ur_lat |
||||
_pyngl.mpCenterLatF = 0.0 |
||||
_pyngl.mpCenterLonF = self.stand_lon |
||||
|
||||
return _pyngl |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
if not basemap_enabled(): |
||||
return None |
||||
|
||||
_basemap = Basemap(projection = "merc", |
||||
lon_0 = self.stand_lon, |
||||
lat_0 = self.moad_cen_lat, |
||||
lat_ts = self._lat_ts, |
||||
llcrnrlat = self.ll_lat, |
||||
urcrnrlat = self.ur_lat, |
||||
llcrnrlon = self.ll_lon, |
||||
urcrnrlon = self.ur_lon, |
||||
rsphere = Constants.WRF_EARTH_RADIUS, |
||||
resolution = 'l') |
||||
|
||||
return _basemap |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
if not cartopy_enabled(): |
||||
return None |
||||
|
||||
if self._lat_ts == 0.0: |
||||
_cartopy = crs.Mercator( |
||||
central_longitude = self.stand_lon, |
||||
globe = self._globe) |
||||
|
||||
else: |
||||
_cartopy = MercatorWithLatTsProj( |
||||
central_longitude = self.stand_lon, |
||||
latitude_true_scale = self._lat_ts, |
||||
globe = self._globe) |
||||
|
||||
return _cartopy |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
|
||||
# Need to modify the extents for the new projection |
||||
pc = crs.PlateCarree() |
||||
xs, ys, zs = self._cartopy.transform_points(pc, |
||||
np.array([self.ll_lon, self.ur_lon]), |
||||
np.array([self.ll_lat, self.ur_lat])).T |
||||
|
||||
_xlimits = xs.tolist() |
||||
_ylimits = ys.tolist() |
||||
|
||||
return (_xlimits, _ylimits) |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
|
||||
_proj4 = ("+proj=merc +units=meters +a={} +b={} " |
||||
"+lon_0={} +lat_ts={}".format( |
||||
Constants.WRF_EARTH_RADIUS, |
||||
Constants.WRF_EARTH_RADIUS, |
||||
self.stand_lon, |
||||
self._lat_ts)) |
||||
|
||||
return _proj4 |
||||
|
||||
class PolarStereographicProj(WrfProj): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
super(PolarStereographicProj, self).__init__(bottom_left, |
||||
top_right, lats, lons, **proj_params) |
||||
self._hemi = -90. if self.truelat1 < 0 else 90. |
||||
self._lat_ts = (None |
||||
if _ismissing(self.truelat1) |
||||
else self.truelat1) |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
_cf_params = {} |
||||
_cf_params["grid_mapping_name"] = "polar_stereographic" |
||||
_cf_params["straight_vertical_longitude_from_pole"] = ( |
||||
self.stand_lon) |
||||
_cf_params["standard_parallel"] = self.truelat1 |
||||
_cf_params["latitude_of_projection_origin"] = self._hemi |
||||
|
||||
return _cf_params |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
if not pyngl_enabled(): |
||||
return None |
||||
|
||||
_pyngl = Resources() |
||||
_pyngl.mpProjection = "Stereographic" |
||||
_pyngl.mpDataBaseVersion = "MediumRes" |
||||
_pyngl.mpLimitMode = "Corners" |
||||
_pyngl.mpLeftCornerLonF = self.ll_lon |
||||
_pyngl.mpLeftCornerLatF = self.ll_lat |
||||
_pyngl.mpRightCornerLonF = self.ur_lon |
||||
_pyngl.mpRightCornerLatF = self.ur_lat |
||||
|
||||
_pyngl.mpCenterLonF = self.stand_lon |
||||
if self._hemi > 0: |
||||
_pyngl.mpCenterLatF = 90.0 |
||||
else: |
||||
_pyngl.mpCenterLatF = -90.0 |
||||
|
||||
return _pyngl |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
if not basemap_enabled(): |
||||
return None |
||||
|
||||
_basemap = Basemap(projection = "stere", |
||||
lon_0 = self.stand_lon, |
||||
lat_0 = self._hemi, |
||||
lat_ts = self._lat_ts, |
||||
llcrnrlat = self.ll_lat, |
||||
urcrnrlat = self.ur_lat, |
||||
llcrnrlon = self.ll_lon, |
||||
urcrnrlon = self.ur_lon, |
||||
rsphere = Constants.WRF_EARTH_RADIUS, |
||||
resolution = 'l') |
||||
|
||||
return _basemap |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
if not cartopy_enabled(): |
||||
return None |
||||
|
||||
_cartopy = crs.Stereographic(central_latitude=self._hemi, |
||||
central_longitude=self.stand_lon, |
||||
true_scale_latitude=self._lat_ts, |
||||
globe=self._globe) |
||||
return _cartopy |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
# Need to modify the extents for the new projection |
||||
pc = crs.PlateCarree() |
||||
xs, ys, zs = self._cartopy.transform_points(pc, |
||||
np.array([self.ll_lon, self.ur_lon]), |
||||
np.array([self.ll_lat, self.ur_lat])).T |
||||
|
||||
_xlimits = xs.tolist() |
||||
_ylimits = ys.tolist() |
||||
|
||||
return (_xlimits, _ylimits) |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
_proj4 = ("+proj=stere +units=meters +a={} +b={} " |
||||
"+lat0={} +lon_0={} +lat_ts={}".format( |
||||
Constants.WRF_EARTH_RADIUS, |
||||
Constants.WRF_EARTH_RADIUS, |
||||
self._hemi, |
||||
self.stand_lon, |
||||
self._lat_ts)) |
||||
|
||||
return _proj4 |
||||
|
||||
|
||||
|
||||
class LatLonProj(WrfProj): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
super(LatLonProj, self).__init__(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
_cf_params = {} |
||||
_cf_params["grid_mapping_name"] = "latitude_longitude" |
||||
return _cf_params |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
if not pyngl_enabled(): |
||||
return None |
||||
|
||||
_pyngl = Resources() |
||||
_pyngl.mpProjection = "CylindricalEquidistant" |
||||
_pyngl.mpDataBaseVersion = "MediumRes" |
||||
_pyngl.mpLimitMode = "Corners" |
||||
_pyngl.mpLeftCornerLonF = self.ll_lon |
||||
_pyngl.mpLeftCornerLatF = self.ll_lat |
||||
_pyngl.mpRightCornerLonF = self.ur_lon |
||||
_pyngl.mpRightCornerLatF = self.ur_lat |
||||
_pyngl.mpCenterLonF = self.stand_lon |
||||
_pyngl.mpCenterLatF = self.moad_cen_lat |
||||
|
||||
return _pyngl |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
if not basemap_enabled(): |
||||
return None |
||||
|
||||
_basemap = Basemap(projection = "cyl", |
||||
lon_0 = self.stand_lon, |
||||
lat_0 = self.moad_cen_lat, |
||||
llcrnrlat = self.ll_lat, |
||||
urcrnrlat = self.ur_lat, |
||||
llcrnrlon = self.ll_lon, |
||||
urcrnrlon = self.ur_lon, |
||||
rsphere = Constants.WRF_EARTH_RADIUS, |
||||
resolution = 'l') |
||||
|
||||
return _basemap |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
if not cartopy_enabled(): |
||||
return None |
||||
|
||||
_cartopy = crs.PlateCarree(central_longitude=self.stand_lon, |
||||
globe=self._globe) |
||||
|
||||
return _cartopy |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
return ([self.ll_lon, self.ur_lon], [self.ll_lat, self.ur_lat]) |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
_proj4 = ("+proj=eqc +units=meters +a={} +b={} " |
||||
"+lon_0={}".format(Constants.WRF_EARTH_RADIUS, |
||||
Constants.WRF_EARTH_RADIUS, |
||||
self.stand_lon)) |
||||
return _proj4 |
||||
|
||||
# Notes (may not be correct since this projection confuses me): |
||||
# Each projection system handles this differently. |
||||
# 1) In WRF, if following the WPS instructions, POLE_LON is mainly used to |
||||
# determine north or south hemisphere. In other words, it determines if |
||||
# the globe is tipped toward or away from you. If a non-0 or non-180 |
||||
# value is chosen, PyNGL cannot plot it. |
||||
# 2) In WRF, POLE_LAT is always positive, but should be negative in the |
||||
# proj4 based systems when using the southern hemisphere projections. |
||||
# 3) In cartopy, pole_longitude is used to describe the dateline, which |
||||
# is 180 degrees away from the normal central (standard) longitude |
||||
# (e.g. center of the projection), according to the cartopy developer. |
||||
# 4) In basemap, lon_0 should be set to the central (standard) longitude. |
||||
# 5) In either cartopy, basemap or pyngl, I'm not sure that projections with |
||||
# a pole_lon not equal to 0 or 180 can be plotted. Hopefully people |
||||
# follow the WPS instructions, otherwise I need to see a sample file and |
||||
# a lot of rum. |
||||
# 6) For items in 3 - 4, the "longitude" (lon_0 or pole_longitude) is |
||||
# determined by WRF's |
||||
# STAND_LON values, with the following calculations based on hemisphere: |
||||
# BASEMAP: NH: -STAND_LON; SH: 180.0 - STAND_LON |
||||
# CARTOPY: NH: -STAND_LON - 180.; SH: -STAND_LON |
||||
# 9) For PYNGL/NCL, you only need to set the center lat and center lon, |
||||
# Center lat is the offset of the pole from +/- 90 degrees. Center |
||||
# lon is -STAND_LON in NH and 180.0 - STAND_LON in SH. |
||||
# 10) It also appears that NetCDF CF has no clear documentation on what |
||||
# each parameter means. Going to assume it is the same as basemap, since |
||||
# basemap appears to mirror the WMO way of doing things (tilt earth, then |
||||
# spin globe). |
||||
# 11) Basemap and cartopy produce projections that differ in their extent |
||||
# calculations by either using negative values or 0-360 (basemap). For |
||||
# this reason, the proj4 string for this class will use cartopy's values |
||||
# to keep things in the -180 to 180, -90 to 90 range. |
||||
# 12) This projection makes me sad. |
||||
class RotLatLonProj(WrfProj): |
||||
def __init__(self, bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
super(RotLatLonProj, self).__init__(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
|
||||
# Need to determine hemisphere, typically pole_lon is 0 for southern |
||||
# hemisphere, 180 for northern hemisphere. If not, going to have |
||||
# to guess based on other parameters, but hopefully people follow |
||||
# the WPS instructions and this never happens. |
||||
self._north = True |
||||
if self.pole_lon is not None: |
||||
if self.pole_lon == 0.: |
||||
self._north = False |
||||
elif self.pole_lon != 180.: |
||||
if self.moad_cen_lat is not None and self.moad_cen_lat < 0.0: |
||||
# Only probably true |
||||
self._north = False |
||||
else: |
||||
if self.moad_cen_lat is not None and self.moad_cen_lat < 0.0: |
||||
# Only probably true |
||||
self._north = False |
||||
|
||||
if self.pole_lat is not None and self.stand_lon is not None: |
||||
self._pyngl_cen_lat = (90. - self.pole_lat if self._north |
||||
else self.pole_lat - 90.0) |
||||
self._pyngl_cen_lon = (-self.stand_lon if self._north |
||||
else 180.0 - self.stand_lon) |
||||
self._bm_lon_0 = (-self.stand_lon if self._north |
||||
else 180.0 - self.stand_lon) |
||||
self._bm_cart_pole_lat = (self.pole_lat if self._north |
||||
else -self.pole_lat ) |
||||
# The important point is that pole longitude is the position |
||||
# of the dateline of the new projection, not its central |
||||
# longitude (per the creator of cartopy). This is based on |
||||
# how it's handled by agencies like WMO, but not proj4. |
||||
self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north |
||||
else -self.stand_lon) |
||||
else: |
||||
self._pyngl_cen_lat = self.moad_cen_lat |
||||
self._pyngl_cen_lon = self.stand_lon |
||||
self._bm_cart_pole_lat = (90.0 - self.moad_cen_lat if self._north |
||||
else -90.0 - self.moad_cen_lat) |
||||
self._bm_lon_0 = (-self.stand_lon if self._north |
||||
else 180.0 - self.stand_lon) |
||||
self._cart_pole_lon = (-self.stand_lon - 180.0 if self._north |
||||
else -self.stand_lon) |
||||
|
||||
@property |
||||
def _cf_params(self): |
||||
_cf_params = {} |
||||
# Assuming this follows the same guidelines as cartopy |
||||
_cf_params["grid_mapping_name"] = "rotated_latitude_longitude" |
||||
_cf_params["grid_north_pole_latitude"] = self._bm_cart_pole_lat |
||||
_cf_params["grid_north_pole_longitude"] = self.pole_lon |
||||
_cf_params["north_pole_grid_longitude"] = self._bm_lon_0 |
||||
|
||||
return _cf_params |
||||
|
||||
@property |
||||
def _pyngl(self): |
||||
if not pyngl_enabled(): |
||||
return None |
||||
|
||||
_pyngl = Resources() |
||||
_pyngl.mpProjection = "CylindricalEquidistant" |
||||
_pyngl.mpDataBaseVersion = "MediumRes" |
||||
_pyngl.mpLimitMode = "Corners" |
||||
_pyngl.mpLeftCornerLonF = self.ll_lon |
||||
_pyngl.mpLeftCornerLatF = self.ll_lat |
||||
_pyngl.mpRightCornerLonF = self.ur_lon |
||||
_pyngl.mpRightCornerLatF = self.ur_lat |
||||
_pyngl.mpCenterLatF = self._pyngl_cen_lat |
||||
_pyngl.mpCenterLonF = self._pyngl_cen_lon |
||||
|
||||
return _pyngl |
||||
|
||||
@property |
||||
def _basemap(self): |
||||
if not basemap_enabled(): |
||||
return None |
||||
|
||||
_basemap = Basemap(projection = "rotpole", |
||||
o_lat_p = self._bm_cart_pole_lat, |
||||
o_lon_p = self.pole_lon, |
||||
llcrnrlat = self.ll_lat, |
||||
urcrnrlat = self.ur_lat, |
||||
llcrnrlon = self.ll_lon, |
||||
urcrnrlon = self.ur_lon, |
||||
lon_0 = self._bm_lon_0, |
||||
rsphere = Constants.WRF_EARTH_RADIUS, |
||||
resolution = 'l') |
||||
return _basemap |
||||
|
||||
@property |
||||
def _cartopy(self): |
||||
if not cartopy_enabled(): |
||||
return None |
||||
|
||||
_cartopy = crs.RotatedPole( |
||||
pole_longitude=self._cart_pole_lon, |
||||
pole_latitude=self._bm_cart_pole_lat, |
||||
central_rotated_longitude=( |
||||
180.0 - self.pole_lon), # Probably |
||||
globe = self._globe) |
||||
return _cartopy |
||||
|
||||
@property |
||||
def _cart_extents(self): |
||||
# Need to modify the extents for the new projection |
||||
pc = crs.PlateCarree() |
||||
xs, ys, zs = self._cartopy.transform_points(pc, |
||||
np.array([self.ll_lon, self.ur_lon]), |
||||
np.array([self.ll_lat, self.ur_lat])).T |
||||
|
||||
_xlimits = xs.tolist() |
||||
_ylimits = ys.tolist() |
||||
|
||||
return (_xlimits, _ylimits) |
||||
|
||||
@property |
||||
def _proj4(self): |
||||
_proj4 = ("+proj=ob_tran +o_proj=latlon " |
||||
"+a={} +b={} +to_meter={} +o_lon_p={} +o_lat_p={} " |
||||
"+lon_0={}".format(Constants.WRF_EARTH_RADIUS, |
||||
Constants.WRF_EARTH_RADIUS, |
||||
math.radians(1), |
||||
180.0 - self.pole_lon, |
||||
self._bm_cart_pole_lat, |
||||
180.0 + self._cart_pole_lon)) |
||||
|
||||
return _proj4 |
||||
|
||||
def getproj(bottom_left=None, top_right=None, |
||||
lats=None, lons=None, **proj_params): |
||||
|
||||
proj_type = proj_params.get("MAP_PROJ", 0) |
||||
if proj_type == 1: |
||||
return LambertConformalProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
elif proj_type == 2: |
||||
return PolarStereographicProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
elif proj_type == 3: |
||||
return MercatorProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
elif proj_type == 0 or proj_type == 6: |
||||
if (proj_params.get("POLE_LAT", None) == 90. |
||||
and proj_params.get("POLE_LON", None) == 0.): |
||||
return LatLonProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
else: |
||||
return RotLatLonProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
else: |
||||
# Unknown projection |
||||
return WrfProj(bottom_left, top_right, |
||||
lats, lons, **proj_params) |
||||
|
||||
|
Binary file not shown.
@ -0,0 +1,214 @@
@@ -0,0 +1,214 @@
|
||||
import unittest as ut |
||||
from os.path import join, basename |
||||
|
||||
import numpy as np |
||||
import matplotlib.pyplot as plt |
||||
import matplotlib.cm as cm |
||||
|
||||
from netCDF4 import Dataset as NetCDF |
||||
|
||||
|
||||
PYNGL = True |
||||
try: |
||||
import Ngl |
||||
except ImportError: |
||||
PYNGL = False |
||||
|
||||
BASEMAP = True |
||||
try: |
||||
import mpl_toolkits.basemap |
||||
except ImportError: |
||||
BASEMAP = False |
||||
|
||||
CARTOPY = True |
||||
try: |
||||
from cartopy import crs, feature |
||||
except ImportError: |
||||
CARTOPY = False |
||||
|
||||
|
||||
from wrf.var import (get_proj_params, getproj, RotLatLonProj, |
||||
PolarStereographicProj) |
||||
|
||||
FILE_DIR = "/Users/ladwig/Documents/wrf_files/" |
||||
WRF_FILES = [ |
||||
join(FILE_DIR,"wrfout_d01_2016-02-25_18_00_00"), |
||||
join(FILE_DIR, "wrfout_d01_2008-09-29_23-30-00"), |
||||
join(FILE_DIR, "wrfout_d01_2010-06-13_21:00:00")] |
||||
|
||||
def nz_proj(): |
||||
lats = np.array([[-47.824014, -47.824014], |
||||
[-32.669853, -32.669853]]) |
||||
lons = np.array([[163.839595, -179.693502], |
||||
[163.839595, -179.693502]]) |
||||
|
||||
params = {"MAP_PROJ" : 6, |
||||
"CEN_LAT" : -41.814869, |
||||
"CEN_LON" : 179.693502, |
||||
"TRUELAT1" : 0, |
||||
"TRUELAT2": 0, |
||||
"MOAD_CEN_LAT" : -41.814869, |
||||
"STAND_LON" : 180.0 - 179.693502, |
||||
"POLE_LAT" : 48.185131, |
||||
"POLE_LON" : 0.0} |
||||
|
||||
return lats, lons, RotLatLonProj(lats=lats, lons=lons, **params) |
||||
|
||||
def argentina_proj(): |
||||
lats = np.array([[-57.144064, -57.144064], |
||||
[-21.154470, -21.154470]]) |
||||
lons = np.array([[-86.893797, -37.089724], |
||||
[-86.893797, -37.089724]]) |
||||
|
||||
params = {"MAP_PROJ" : 6, |
||||
"CEN_LAT" : -39.222954, |
||||
"CEN_LON" : -65.980109, |
||||
"TRUELAT1" : 0, |
||||
"TRUELAT2": 0, |
||||
"MOAD_CEN_LAT" : -39.222954, |
||||
"STAND_LON" : 180.0 - -65.980109, |
||||
"POLE_LAT" : 90 + -39.222954, |
||||
"POLE_LON" : 0.0} |
||||
|
||||
return lats, lons, RotLatLonProj(lats=lats, lons=lons, **params) |
||||
|
||||
def south_polar_proj(): |
||||
lats = np.array([[-30.0,-30.0], |
||||
[-30.0,-30.0]]) |
||||
lons = np.array([[-120, 60], |
||||
[-120, 60]]) |
||||
|
||||
params = {"MAP_PROJ" : 2, |
||||
"CEN_LAT" : -90.0, |
||||
"CEN_LON" : 0, |
||||
"TRUELAT1" : -10.0, |
||||
"MOAD_CEN_LAT" : -90.0, |
||||
"STAND_LON" : 0} |
||||
|
||||
return lats, lons, PolarStereographicProj(lats=lats, lons=lons, **params) |
||||
|
||||
def north_polar_proj(): |
||||
lats = np.array([[30.0,30.0], |
||||
[30.0,30.0]]) |
||||
lons = np.array([[-45, 140], |
||||
[-45, 140]]) |
||||
|
||||
params = {"MAP_PROJ" : 2, |
||||
"CEN_LAT" : 90.0, |
||||
"CEN_LON" : 10, |
||||
"TRUELAT1" : 10.0, |
||||
"MOAD_CEN_LAT" : 90.0, |
||||
"STAND_LON" : 10} |
||||
|
||||
return lats, lons, PolarStereographicProj(lats=lats, lons=lons, **params) |
||||
|
||||
|
||||
def dateline_rot_proj(): |
||||
lats = np.array([[60.627974, 60.627974], |
||||
[71.717521, 71.717521]]) |
||||
lons = np.array([[170.332771, -153.456292], |
||||
[170.332771, -153.456292]]) |
||||
|
||||
params = {"MAP_PROJ" : 6, |
||||
"CEN_LAT" : 66.335764, |
||||
"CEN_LON" : -173.143792, |
||||
"TRUELAT1" : 0, |
||||
"TRUELAT2": 0, |
||||
"MOAD_CEN_LAT" : 66.335764, |
||||
"STAND_LON" : 173.143792, |
||||
"POLE_LAT" : 90.0 - 66.335764, |
||||
"POLE_LON" : 180.0} |
||||
return lats, lons, RotLatLonProj(lats=lats, lons=lons, **params) |
||||
|
||||
class WRFProjTest(ut.TestCase): |
||||
longMessage = True |
||||
|
||||
def make_test(wrf_file=None, fixed_case=None): |
||||
if wrf_file is not None: |
||||
ncfile = NetCDF(wrf_file) |
||||
lats, lons, proj_params = get_proj_params(ncfile) |
||||
proj = getproj(lats=lats, lons=lons, **proj_params) |
||||
name_suffix = basename(wrf_file) |
||||
elif fixed_case is not None: |
||||
name_suffix = fixed_case |
||||
if fixed_case == "south_rot": |
||||
lats, lons, proj = nz_proj() |
||||
elif fixed_case == "arg_rot": |
||||
lats, lons, proj = argentina_proj() |
||||
elif fixed_case == "south_polar": |
||||
lats, lons, proj = south_polar_proj() |
||||
elif fixed_case == "north_polar": |
||||
lats, lons, proj = north_polar_proj() |
||||
elif fixed_case == "dateline_rot": |
||||
lats,lons,proj = dateline_rot_proj() |
||||
|
||||
print "wrf proj4: {}".format(proj.proj4()) |
||||
if PYNGL: |
||||
# PyNGL plotting |
||||
wks_type = "png" |
||||
wks = Ngl.open_wks(wks_type,"pyngl_{}".format(name_suffix)) |
||||
mpres = proj.pyngl() |
||||
map = Ngl.map(wks,mpres) |
||||
|
||||
Ngl.delete_wks(wks) |
||||
|
||||
if BASEMAP: |
||||
# Basemap plotting |
||||
fig = plt.figure(figsize=(10,10)) |
||||
ax = fig.add_axes([0.1,0.1,0.8,0.8]) |
||||
|
||||
# Define and plot the meridians and parallels |
||||
min_lat = np.amin(lats) |
||||
max_lat = np.amax(lats) |
||||
min_lon = np.amin(lons) |
||||
max_lon = np.amax(lons) |
||||
|
||||
parallels = np.arange(np.floor(min_lat), np.ceil(max_lat), |
||||
(max_lat - min_lat)/5.0) |
||||
meridians = np.arange(np.floor(min_lon), np.ceil(max_lon), |
||||
(max_lon - min_lon)/5.0) |
||||
|
||||
bm = proj.basemap() |
||||
bm.drawcoastlines(linewidth=.5) |
||||
#bm.drawparallels(parallels,labels=[1,1,1,1],fontsize=10) |
||||
#bm.drawmeridians(meridians,labels=[1,1,1,1],fontsize=10) |
||||
print "basemap proj4: {}".format(bm.proj4string) |
||||
plt.savefig("basemap_{}.png".format(name_suffix)) |
||||
plt.close(fig) |
||||
|
||||
if CARTOPY: |
||||
# Cartopy plotting |
||||
fig = plt.figure(figsize=(10,10)) |
||||
ax = plt.axes([0.1,0.1,0.8,0.8], projection=proj.cartopy()) |
||||
print "cartopy proj4: {}".format(proj.cartopy().proj4_params) |
||||
|
||||
ax.coastlines('50m', linewidth=0.8) |
||||
#print proj.x_extents() |
||||
#print proj.y_extents() |
||||
ax.set_xlim(proj.cartopy_xlim()) |
||||
ax.set_ylim(proj.cartopy_ylim()) |
||||
ax.gridlines() |
||||
plt.savefig("cartopy_{}.png".format(name_suffix)) |
||||
plt.close(fig) |
||||
|
||||
if __name__ == "__main__": |
||||
for wrf_file in WRF_FILES: |
||||
test_func = make_test(wrf_file=wrf_file) |
||||
setattr(WRFProjTest, "test_proj", test_func) |
||||
|
||||
test_func2 = make_test(fixed_case="south_rot") |
||||
setattr(WRFProjTest, "test_south_rot", test_func2) |
||||
|
||||
test_func3 = make_test(fixed_case="arg_rot") |
||||
setattr(WRFProjTest, "test_arg_rot", test_func3) |
||||
|
||||
test_func4 = make_test(fixed_case="south_polar") |
||||
setattr(WRFProjTest, "test_south_polar", test_func4) |
||||
|
||||
test_func5 = make_test(fixed_case="north_polar") |
||||
setattr(WRFProjTest, "test_north_polar", test_func5) |
||||
|
||||
test_func6 = make_test(fixed_case="dateline_rot") |
||||
setattr(WRFProjTest, "test_dateline_rot", test_func6) |
||||
|
||||
ut.main() |
Loading…
Reference in new issue